diff --git a/resources/languages/resources_bg.properties b/resources/languages/resources_bg.properties index d367e55a4..a97077605 100644 --- a/resources/languages/resources_bg.properties +++ b/resources/languages/resources_bg.properties @@ -119,7 +119,7 @@ service.gui.INVITATION=Текст на поканата service.gui.INVITATION_RECEIVED=Получена е покана service.gui.INVITATION_RECEIVED_MSG={0} ви кани да влезете в стая {1}. Можете да приемете, откажете или да пренебрегнете тази покана. service.gui.INVITE=&Покана -service.gui.INVITE_CONTACT_MSG=Моля въведете името на потребителя който бихте желали да поканите. Ако желаете можете да въведете и обяснение към поканата. +service.gui.INVITE_CONTACT_MSG=Моля въведете името на потребителя, който бихте желали да поканите. Ако желаете, можете да въведете и обяснение към поканата. service.gui.INVITE_CONTACT_TO_CHAT=Покани контакт на чат service.gui.JOIN=&Влез service.gui.JOIN_AS=В&лез като 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 91e2902ea..b1025f6d6 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 @@ -390,6 +390,7 @@ public CreateConferenceCallThread( this.protocolProvider = protocolProvider; } + @Override public void run() { OperationSetTelephonyConferencing confOpSet @@ -405,19 +406,31 @@ public void run() if (confOpSet == null) return; + Throwable exception = null; + try { confOpSet.createConfCall(callees); } - catch (OperationNotSupportedException e) + catch (OperationFailedException ofe) + { + exception = ofe; + } + catch (OperationNotSupportedException onse) + { + exception = onse; + } + if (exception != null) { - logger.error("Failed to create conference call. " + e); + logger.error("Failed to create conference call. " + exception); - new ErrorDialog(null, - GuiActivator.getResources() - .getI18NString("service.gui.ERROR"), - e.getMessage(), - ErrorDialog.ERROR).showDialog(); + new ErrorDialog( + null, + GuiActivator + .getResources().getI18NString("service.gui.ERROR"), + exception.getMessage(), + ErrorDialog.ERROR) + .showDialog(); } } } @@ -438,6 +451,7 @@ public InviteToConferenceCallThread(String[] callees, Call call) this.call = call; } + @Override public void run() { OperationSetTelephonyConferencing confOpSet @@ -456,19 +470,33 @@ public void run() for (String callee : callees) { + Throwable exception = null; + try { confOpSet.inviteCalleeToCall(callee, call); } - catch (OperationNotSupportedException e) + catch (OperationFailedException ofe) { - logger.error("Failed to invite callee: " + callee, e); - - new ErrorDialog(null, - GuiActivator.getResources() - .getI18NString("service.gui.ERROR"), - e.getMessage(), - ErrorDialog.ERROR).showDialog(); + exception = ofe; + } + catch (OperationNotSupportedException onse) + { + exception = onse; + } + if (exception != null) + { + logger + .error("Failed to invite callee: " + callee, exception); + + new ErrorDialog( + null, + GuiActivator + .getResources() + .getI18NString("service.gui.ERROR"), + exception.getMessage(), + ErrorDialog.ERROR) + .showDialog(); } } } diff --git a/src/net/java/sip/communicator/impl/gui/main/menus/ToolsMenu.java b/src/net/java/sip/communicator/impl/gui/main/menus/ToolsMenu.java index 16cf380f5..fd9993523 100644 --- a/src/net/java/sip/communicator/impl/gui/main/menus/ToolsMenu.java +++ b/src/net/java/sip/communicator/impl/gui/main/menus/ToolsMenu.java @@ -162,15 +162,11 @@ private void registerMenuItems() GuiActivator.getResources().getI18NString( "service.gui.CREATE_CONFERENCE_CALL") + " (coming soon)"); - // Disables the conference menu item until the work on this feature is - // completed and fully tested. - conferenceMenuItem.setEnabled(false); - - this.add(conferenceMenuItem); conferenceMenuItem.setMnemonic(GuiActivator.getResources() .getI18nMnemonic("service.gui.CREATE_CONFERENCE_CALL")); conferenceMenuItem.setName("conference"); conferenceMenuItem.addActionListener(this); + this.add(conferenceMenuItem); } private boolean registerConfigMenuItemMacOSX() diff --git a/src/net/java/sip/communicator/impl/protocol/sip/CallPeerMediaHandler.java b/src/net/java/sip/communicator/impl/protocol/sip/CallPeerMediaHandler.java index 8bb68e3cf..2bea8fbce 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/CallPeerMediaHandler.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/CallPeerMediaHandler.java @@ -1094,6 +1094,30 @@ private StreamConnector createStreamConnector() return connector; } + /** + * Gets the MediaStream of this CallPeerMediaHandler which + * is of a specific MediaType. If this instance doesn't have such a + * MediaStream, returns null + * + * @param mediaType the MediaType of the MediaStream to + * retrieve + * @return the MediaStream of this CallPeerMediaHandler + * which is of the specified mediaType if this instance has such a + * MediaStream; otherwise, null + */ + MediaStream getStream(MediaType mediaType) + { + switch (mediaType) + { + case AUDIO: + return audioStream; + case VIDEO: + return videoStream; + default: + throw new IllegalArgumentException("mediaType"); + } + } + /** * Returns the StreamConnector instance that this media handler * should use for streams of the specified mediaType. The method diff --git a/src/net/java/sip/communicator/impl/protocol/sip/CallPeerSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/CallPeerSipImpl.java index 2228984fd..6fdea44f0 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/CallPeerSipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/CallPeerSipImpl.java @@ -24,6 +24,7 @@ * Our SIP implementation of the default CallPeer; * * @author Emil Ivov + * @author Lubomir Marinov */ public class CallPeerSipImpl extends AbstractCallPeer @@ -80,12 +81,6 @@ public class CallPeerSipImpl */ private InetSocketAddress transportAddress = null; - /** - * A reference to the SipMessageFactory instance that we should - * use when creating requests. - */ - private final SipMessageFactory messageFactory; - /** * The media handler class handles all media management for a single * CallPeer. This includes initializing and configuring streams, @@ -101,6 +96,19 @@ public class CallPeerSipImpl */ private PropertyChangeListener mediaHandlerPropertyChangeListener; + /** + * A reference to the SipMessageFactory instance that we should + * use when creating requests. + */ + private final SipMessageFactory messageFactory; + + /** + * The List of MethodProcessorListeners interested in how + * this CallPeer processes SIP signaling. + */ + private final List methodProcessorListeners + = new LinkedList(); + /** * The List of PropertyChangeListeners listening to this * CallPeer for changes in the values of its properties related to @@ -492,6 +500,13 @@ public void processReInvite(ServerTransaction serverTransaction) { response = messageFactory.createResponse(Response.OK, invite); + /* + * If the local peer represented by the Call of this CallPeer is + * acting as a conference focus, it must indicate it in its Contact + * header. + */ + reflectConferenceFocus(response); + String sdpAnswer; if(sdpOffer != null) sdpAnswer = getMediaHandler().processOffer( sdpOffer ); @@ -517,6 +532,8 @@ public void processReInvite(ServerTransaction serverTransaction) } reevalRemoteHoldStatus(); + + fireRequestProcessed(invite, response); } /** @@ -878,6 +895,8 @@ public void processInviteOK(ClientTransaction clientTransaction, // change status if (!CallPeerState.isOnHold(getState())) setState(CallPeerState.CONNECTED); + + fireResponseProcessed(ok, null); } /** @@ -1098,11 +1117,18 @@ public synchronized void answer() } ServerTransaction serverTransaction = (ServerTransaction) transaction; + Request invite = serverTransaction.getRequest(); Response ok = null; try { - ok = messageFactory.createResponse( - Response.OK, serverTransaction.getRequest()); + ok = messageFactory.createResponse(Response.OK, invite); + + /* + * If the local peer represented by the Call of this CallPeer is + * acting as a conference focus, it must indicate it in its Contact + * header. + */ + reflectConferenceFocus(ok); } catch (ParseException ex) { @@ -1137,7 +1163,6 @@ public synchronized void answer() // extract the SDP description. // beware: SDP description may be in ACKs so it could be that there's // nothing here - bug report Laurent Michel - Request invite = getLatestInviteTransaction().getRequest(); ContentLengthHeader cl = invite.getContentLength(); if (cl != null && cl.getContentLength() > 0) { @@ -1182,6 +1207,8 @@ public synchronized void answer() "Failed to send an OK response to an INVITE request", OperationFailedException.NETWORK_FAILURE, ex, logger); } + + fireRequestProcessed(invite, ok); } /** @@ -1214,9 +1241,21 @@ public void putOnHold(boolean onHold) reevalLocalHoldStatus(); } + /** + * Sends a reINVITE request to this CallPeer within its current + * Dialog. + * + * @throws OperationFailedException if sending the reINVITE request fails + */ + void sendReInvite() + throws OperationFailedException + { + sendReInvite(getMediaHandler().createOffer()); + } + /** * Sends a reINVITE request with a specific sdpOffer (description) - * within the current Dialog with a the call peer represented by + * within the current Dialog with the call peer represented by * this instance. * * @param sdpOffer the offer that we'd like to use for the newly created @@ -1233,8 +1272,16 @@ private void sendReInvite(String sdpOffer) try { + // Content-Type invite.setContent(sdpOffer, getProtocolProvider().getHeaderFactory() .createContentTypeHeader("application", "sdp")); + + /* + * If the local peer represented by the Call of this CallPeer is + * acting as a conference focus, it must indicate it in its Contact + * header. + */ + reflectConferenceFocus(invite); } catch (ParseException ex) { @@ -1260,17 +1307,27 @@ private void sendReInvite(String sdpOffer) public void invite() throws OperationFailedException { - ClientTransaction inviteTran; try { - inviteTran = (ClientTransaction)getLatestInviteTransaction(); + ClientTransaction inviteTran + = (ClientTransaction) getLatestInviteTransaction(); + Request invite = inviteTran.getRequest(); - ContentTypeHeader contentTypeHeader = getProtocolProvider() - .getHeaderFactory().createContentTypeHeader( - "application", "sdp"); + // Content-Type + ContentTypeHeader contentTypeHeader + = getProtocolProvider() + .getHeaderFactory() + .createContentTypeHeader("application", "sdp"); - inviteTran.getRequest().setContent(getMediaHandler().createOffer(), - contentTypeHeader); + invite + .setContent(getMediaHandler().createOffer(), contentTypeHeader); + + /* + * If the local peer represented by the Call of this CallPeer is + * acting as a conference focus, it must indicate it in its Contact + * header. + */ + reflectConferenceFocus(invite); inviteTran.sendRequest(); if (logger.isDebugEnabled()) @@ -1284,6 +1341,33 @@ public void invite() } } + /** + * Reflects the value of the conferenceFocus property of the + * Call of this CallPeer in the specified SIP + * Message. + * + * @param message the SIP Message in which the value of the + * conferenceFocus property of the Call of this + * CallPeer is to be reflected + * @throws ParseException if modifying the specified SIP Message to + * reflect the conferenceFocus property of the Call of + * this CallPeer fails + */ + private void reflectConferenceFocus(javax.sip.message.Message message) + throws ParseException + { + ContactHeader contactHeader + = (ContactHeader) message.getHeader(ContactHeader.NAME); + + if (contactHeader != null) + { + if (getCall().isConferenceFocus()) + contactHeader.setParameter("isfocus", ""); + else + contactHeader.removeParameter("isfocus"); + } + } + /** * 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 @@ -1430,6 +1514,97 @@ public void removeVideoPropertyChangeListener( } } + /** + * Registers a specific MethodProcessorListener with this + * CallPeer so that it gets notified by this instance about the + * processing of SIP signaling. If the specified listener is + * already registered with this instance, does nothing + * + * @param listener the MethodProcessorListener to be registered + * with this CallPeer so that it gets notified by this instance + * about the processing of SIP signaling + */ + void addMethodProcessorListener(MethodProcessorListener listener) + { + if (listener == null) + throw new NullPointerException("listener"); + + synchronized (methodProcessorListeners) + { + if (!methodProcessorListeners.contains(listener)) + methodProcessorListeners.add(listener); + } + } + + /** + * Notifies the MethodProcessorListeners registered with this + * CallPeer that it has processed a specific SIP Request + * by sending a specific SIP Response. + * + * @param request the SIP Request processed by this + * CallPeer + * @param response the SIP Response this CallPeer sent as + * part of its processing of the specified request + */ + protected void fireRequestProcessed(Request request, Response response) + { + Iterable listeners; + + synchronized (methodProcessorListeners) + { + listeners + = new LinkedList( + methodProcessorListeners); + } + + for (MethodProcessorListener listener : listeners) + listener.requestProcessed(this, request, response); + } + + /** + * Notifies the MethodProcessorListeners registered with this + * CallPeer that it has processed a specific SIP Response + * by sending a specific SIP Request. + * + * @param response the SIP Response processed by this + * CallPeer + * @param request the SIP Request this CallPeer sent as + * part of its processing of the specified response + */ + protected void fireResponseProcessed(Response response, Request request) + { + Iterable listeners; + + synchronized (methodProcessorListeners) + { + listeners + = new LinkedList( + methodProcessorListeners); + } + + for (MethodProcessorListener listener : listeners) + listener.responseProcessed(this, response, request); + } + + /** + * Unregisters a specific MethodProcessorListener from this + * CallPeer so that it no longer gets notified by this instance + * about the processing of SIP signaling. If the specified listener + * is not registered with this instance, does nothing. + * + * @param listener the MethodProcessorListener to be unregistered + * from this CallPeer so that it no longer gets notified by this + * instance about the processing of SIP signaling + */ + void removeMethodProcessorListener(MethodProcessorListener listener) + { + if (listener != null) + synchronized (methodProcessorListeners) + { + methodProcessorListeners.remove(listener); + } + } + /** * Updates this call so that it would record a new transaction and dialog * that have been recreated because of a re-authentication. diff --git a/src/net/java/sip/communicator/impl/protocol/sip/CallSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/CallSipImpl.java index fb6972314..7b5de83c9 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/CallSipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/CallSipImpl.java @@ -36,6 +36,19 @@ public class CallSipImpl private final List callPeers = new Vector(); + /** + * The indicator which determines whether the local peer represented by this + * Call is acting as a conference focus and is thus specifying the + * "isfocus" parameter in the Contact headers of its outgoing SIP + * signaling. + */ + private boolean conferenceFocus = false; + + /** + * Our video streaming policy. + */ + private boolean localVideoAllowed = false; + /** * A reference to the SipMessageFactory instance that we should * use when creating requests. @@ -48,11 +61,6 @@ public class CallSipImpl */ private final OperationSetBasicTelephonySipImpl parentOpSet; - /** - * Our video streaming policy. - */ - private boolean isLocalVideoAllowed = false; - /** * Crates a CallSipImpl instance belonging to sourceProvider and * initiated by CallCreator. @@ -405,8 +413,6 @@ public void processReplacingInvite(SipProvider jainSipProvider, return; } - - //we just accepted the new peer and if we got here then it went well //now let's hangup the other call. try @@ -420,7 +426,6 @@ public void processReplacingInvite(SipProvider jainSipProvider, callPeerToReplace.setState( CallPeerState.FAILED, "Internal Error: " + ex); } - } /** @@ -473,7 +478,7 @@ public CallPeerSipImpl processInvite(SipProvider jainSipProvider, public void setLocalVideoAllowed(boolean allowed) throws OperationFailedException { - this.isLocalVideoAllowed = allowed; + this.localVideoAllowed = allowed; /* * Record the setting locally and notify all peers. @@ -499,7 +504,7 @@ public void setLocalVideoAllowed(boolean allowed) */ public boolean isLocalVideoAllowed() { - return isLocalVideoAllowed; + return localVideoAllowed; } /** @@ -563,4 +568,36 @@ public void removeVideoPropertyChangeListener( peer.removeVideoPropertyChangeListener(listener); } } + + /** + * Gets the indicator which determines whether the local peer represented by + * this Call is acting as a conference focus and thus should send + * the "isfocus" parameter in the Contact headers of its outgoing + * SIP signaling. + * + * @return true if the local peer represented by this Call + * is acting as a conference focus; otherwise, false + */ + boolean isConferenceFocus() + { + return conferenceFocus; + } + + /** + * Sets the indicator which determines whether the local peer represented by + * this Call is acting as a conference focus and thus should send + * the "isfocus" parameter in the Contact headers of its outgoing + * SIP signaling + * + * @param conferenceFocus true if the local peer represented by + * this Call is to act as a conference focus; otherwise, + * false + */ + void setConferenceFocus(boolean conferenceFocus) + { + if (this.conferenceFocus != conferenceFocus) + { + this.conferenceFocus = conferenceFocus; + } + } } diff --git a/src/net/java/sip/communicator/impl/protocol/sip/ConferenceMemberSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/ConferenceMemberSipImpl.java new file mode 100644 index 000000000..85cb44cfa --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/sip/ConferenceMemberSipImpl.java @@ -0,0 +1,161 @@ +/* + * SIP Communicator, 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.sip; + +import net.java.sip.communicator.service.protocol.*; + +/** + * Implements ConferenceMember for the SIP protocol. + * + * @author Lubomir Marinov + */ +public class ConferenceMemberSipImpl + extends AbstractConferenceMember +{ + + /** + * A Public Switched Telephone Network (PSTN) ALERTING or SIP 180 Ringing + * was returned for the outbound call; endpoint is being alerted. + */ + static final String ALERTING = "alerting"; + + /** + * The endpoint is a participant in the conference. Depending on the media + * policies, he/she can send and receive media to and from other + * participants. + */ + static final String CONNECTED = "connected"; + + /** + * Endpoint is dialing into the conference, not yet in the roster (probably + * being authenticated). + */ + static final String DIALING_IN = "dialing-in"; + + /** + * Focus has dialed out to connect the endpoint to the conference, but the + * endpoint is not yet in the roster (probably being authenticated). + */ + static final String DIALING_OUT = "dialing-out"; + + /** + * The endpoint is not a participant in the conference, and no active dialog + * exists between the endpoint and the focus. + */ + static final String DISCONNECTED = "disconnected"; + + /** + * Active signaling dialog exists between an endpoint and a focus, but + * endpoint is "on-hold" for this conference, i.e., he/she is neither + * "hearing" the conference mix nor is his/her media being mixed in the + * conference. + */ + static final String ON_HOLD = "on-hold"; + + /** + * Endpoint is not yet in the session, but it is anticipated that he/she + * will join in the near future. + */ + static final String PENDING = "pending"; + + /** + * The SIP address of this ConferenceMember as specified by the + * conference-info XML received from its conferenceFocusCallPeer. + */ + private final String address; + + /** + * Initializes a new ConferenceMemberSipImpl instance with a + * specific SIP address as indicated by the conference-info XML received + * from its conferenceFocusCallPeer. + * + * @param conferenceFocusCallPeer the CallPeer which is the focus + * of the conference in which the new ConferenceMember participates + * @param address the SIP address of the new instance + */ + public ConferenceMemberSipImpl( + CallPeerSipImpl conferenceFocusCallPeer, + String address) + { + super(conferenceFocusCallPeer); + + if (address == null) + throw new NullPointerException("address"); + this.address = address; + } + + /** + * Gets the SIP address of this ConferenceMember as specified by + * the conference-info XML received from its + * conferenceFocusCallPeer. + * + * @return the SIP address of this ConferenceMember as specified by + * the conference-info XML received from its + * conferenceFocusCallPeer + */ + public String getAddress() + { + return address; + } + + /** + * Overrides {@link AbstractCallPeer#getDisplayName()} in order to return + * the SIP address of this ConferenceMember if the display name is + * empty. + * + * @return if the displayName property of this instance is an empty + * String value, returns the address property of this + * instance; otherwise, returns the value of the displayName + * property of this instance + */ + @Override + public String getDisplayName() + { + String displayName = super.getDisplayName(); + + if ((displayName == null) || (displayName.length() < 1)) + { + String address = getAddress(); + + if ((address != null) && (address.length() > 0)) + return address; + } + return displayName; + } + + /** + * Sets the state property of this ConferenceMember by + * translating it from its conference-info XML endpoint status. + * + * @param endpointStatus the conference-info XML endpoint status of this + * ConferenceMember indicated by its + * conferenceFocusCallPeer + */ + void setEndpointStatus(String endpointStatus) + { + ConferenceMemberState state; + + if (ALERTING.equalsIgnoreCase(endpointStatus)) + state = ConferenceMemberState.ALERTING; + else if (CONNECTED.equalsIgnoreCase(endpointStatus)) + state = ConferenceMemberState.CONNECTED; + else if (DIALING_IN.equalsIgnoreCase(endpointStatus)) + state = ConferenceMemberState.DIALING_IN; + else if (DIALING_OUT.equalsIgnoreCase(endpointStatus)) + state = ConferenceMemberState.DIALING_OUT; + else if (DISCONNECTED.equalsIgnoreCase(endpointStatus)) + state = ConferenceMemberState.DISCONNECTED; + else if (ON_HOLD.equalsIgnoreCase(endpointStatus)) + state = ConferenceMemberState.ON_HOLD; + else if (PENDING.equalsIgnoreCase(endpointStatus)) + state = ConferenceMemberState.PENDING; + else + state = ConferenceMemberState.UNKNOWN; + + setState(state); + } +} diff --git a/src/net/java/sip/communicator/impl/protocol/sip/EventPackageNotifier.java b/src/net/java/sip/communicator/impl/protocol/sip/EventPackageNotifier.java index 311a4f374..6af5d0b21 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/EventPackageNotifier.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/EventPackageNotifier.java @@ -423,23 +423,50 @@ public void notify( Subscription subscription, * @param reason the reason for the specified subscription state * * @throws OperationFailedException if anything goes wrong while sending out - * notifications. + * the notifications. */ public void notifyAll(String subscriptionState, String reason) throws OperationFailedException + { + notifyAll(subscriptionState, reason, null); + } + + /** + * Notifies all targets represented by the Subscriptions managed by + * this instance which are accepted by a specific + * SubscriptionFilter about a specific subscription state and a + * specific reason for that subscription state via NOTIFY requests. + * + * @param subscriptionState the subscription state to be sent to all targets + * represented by the Subscriptions managed by this instance which + * are accepted by filter via NOTIFY requests + * @param reason the reason for the specified subscription state + * @param filter the SubscriptionFilter to pick up the + * Subscriptions managed by this instance to be notified about + * subscriptionState + * @throws OperationFailedException if anything goes wrong while sending out + * the notifications + */ + public void notifyAll( + String subscriptionState, + String reason, + SubscriptionFilter filter) + throws OperationFailedException { for (Subscription subscription : getSubscriptions()) - notify(subscription, subscriptionState, reason); + if ((filter == null) || filter.accept(subscription)) + notify(subscription, subscriptionState, reason); } /** * Processes incoming subscribe requests. * - * @param requestEvent the event containing the request we need to handle. - * - * @return true if we have handled and thus consumed the request - * and false otherwise. + * @param requestEvent the event containing the request we need to handle + * @return true if we have handled and thus consumed the request; + * false, otherwise + * @see MethodProcessor#processRequest(RequestEvent) */ + @Override public boolean processRequest(RequestEvent requestEvent) { Request request = requestEvent.getRequest(); @@ -761,11 +788,12 @@ public boolean processRequest(RequestEvent requestEvent) /** * Handles an incoming response to a request we'vre previously sent. * - * @param responseEvent the event we need to handle. - * - * @return true in case we've handles and thus consumed the event - * and false otherwise. + * @param responseEvent the event we need to handle + * @return true in case we've handles and thus consumed the event; + * false, otherwise + * @see MethodProcessor#processResponse(ResponseEvent) */ + @Override public boolean processResponse(ResponseEvent responseEvent) { Response response = responseEvent.getResponse(); @@ -959,6 +987,29 @@ protected abstract byte[] createNotifyContent( String reason); } + /** + * Represents a filter for Subscriptions i.e. it determines whether + * a specific Subscription is to be selected or deselected by the + * caller of the filter. + */ + public interface SubscriptionFilter + { + + /** + * Determines whether the specified Subscription is accepted by + * this SubscriptionFilter i.e. whether the caller of this + * instance is to include or exclude the specified Subscription + * from their list of processed Subscriptions. + * + * @param subscription the Subscription to be checked + * @return true if this SubscriptionFilter accepts + * the specified subscription i.e. the caller of this instance + * should include subscription in their list of processed + * Subscriptions; otherwise, false + */ + public boolean accept(Subscription subscription); + } + /** * Represents a TimerTask which times out a specific * Subscription when its subscription duration expires. diff --git a/src/net/java/sip/communicator/impl/protocol/sip/EventPackageSubscriber.java b/src/net/java/sip/communicator/impl/protocol/sip/EventPackageSubscriber.java index b471d4a56..12f2b062f 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/EventPackageSubscriber.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/EventPackageSubscriber.java @@ -401,7 +401,7 @@ private Subscription getSubscription(String callId) * subscription Address/Request URI and id tag of * its Event header * @throws OperationFailedException if we fail constructing or sending the - * subsctiption request. + * subscription request. */ public void poll(Subscription subscription) throws OperationFailedException @@ -504,15 +504,21 @@ private void populateSubscribeRequest( req.setHeader(expHeader); } - /* - * Implements MethodProcessor#processRequest(RequestEvent). Handles only - * NOTIFY requests because they are the only requests concerning event + /** + * Implements {@link MethodProcessor#processRequest(RequestEvent)}. Handles + * only NOTIFY requests because they are the only requests concerning event * package subscribers and if the processing of a given request requires * event package-specific handling, delivers the request to the matching * Subscription instance. Examples of such event package-specific handling * include handling the termination of an existing Subscription and * processing the bodies of the NOTIFY requests for active Subscriptions. + * + * @param requestEvent a RequestEvent specifying the SIP + * Request to be processed + * @return true if the SIP Request specified by + * requestEvent was processed; otherwise, false */ + @Override public boolean processRequest(RequestEvent requestEvent) { Request request = requestEvent.getRequest(); @@ -704,16 +710,23 @@ public boolean processRequest(RequestEvent requestEvent) return true; } - /* - * Implements MethodProcessor#processResponse(ResponseEvent). Handles only - * responses to SUBSCRIBE requests because they are the only requests - * concerning event package subscribers (and the only requests sent by them, - * for that matter) and if the processing of a given response requires event - * package-specific handling, delivers the response to the matching - * Subscription instance. Examples of such event package-specific handling - * include letting the respective Subscription handle the success or failure - * in the establishment of a subscription. + /** + * Implements {@link MethodProcessor#processResponse(ResponseEvent)}. + * Handles only responses to SUBSCRIBE requests because they are the only + * requests concerning event package subscribers (and the only requests sent + * by them, for that matter) and if the processing of a given response + * requires event package-specific handling, delivers the response to the + * matching Subscription instance. Examples of such event + * package-specific handling include letting the respective + * Subscription handle the success or failure in the establishment + * of a subscription. + * + * @param responseEvent a ResponseEvent specifying the SIP + * Response to be processed + * @return true if the SIP Response specified by + * responseEvent was processed; otherwise, false */ + @Override public boolean processResponse(ResponseEvent responseEvent) { Response response = responseEvent.getResponse(); @@ -995,17 +1008,25 @@ private void removeSubscription(String callId, Subscription subscription) * the SUBSCRIBE request to be created and sent, to be added to * the list of subscriptions managed by this instance * @throws OperationFailedException if we fail constructing or sending the - * subsctiption request. + * subscription request. */ public void subscribe(Subscription subscription) throws OperationFailedException { + Dialog dialog = subscription.getDialog(); + + if ((dialog != null) + && DialogState.TERMINATED.equals(dialog.getState())) + dialog = null; + //create the subscription ClientTransaction subscribeTransaction = null; try { subscribeTransaction - = createSubscription(subscription, subscriptionDuration); + = (dialog == null) + ? createSubscription(subscription, subscriptionDuration) + : createSubscription(subscription, dialog, subscriptionDuration); } catch (OperationFailedException ex) { @@ -1024,7 +1045,10 @@ public void subscribe(Subscription subscription) // send the message try { - subscribeTransaction.sendRequest(); + if (dialog == null) + subscribeTransaction.sendRequest(); + else + dialog.sendRequest(subscribeTransaction); } catch (SipException ex) { diff --git a/src/net/java/sip/communicator/impl/protocol/sip/MethodProcessorListener.java b/src/net/java/sip/communicator/impl/protocol/sip/MethodProcessorListener.java new file mode 100644 index 000000000..cb8278d5e --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/sip/MethodProcessorListener.java @@ -0,0 +1,54 @@ +/* + * SIP Communicator, 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.sip; + +import javax.sip.message.*; + +/** + * Represents a listener which gets notified by the CallPeer it is + * registered with about the processing of SIP signaling that the + * CallPeer performs. + * + * @author Lubomir Marinov + */ +public interface MethodProcessorListener +{ + + /** + * Notifies this MethodProcessorListener that a specific + * CallPeer has processed a specific SIP Request and has + * replied to it with a specific SIP Response. + * + * @param sourceCallPeer the CallPeer which has processed the + * specified SIP Request + * @param request the SIP Request which has been processed by + * sourceCallPeer + * @param response the SIP Response sent by sourceCallPeer + * as a reply to the specified SIP request + */ + public void requestProcessed( + CallPeerSipImpl sourceCallPeer, + Request request, + Response response); + + /** + * Notifies this MethodProcessorListener that a specific + * CallPeer has processed a specific SIP Response and has + * replied to it with a specific SIP Request. + * + * @param sourceCallPeer the CallPeer which has processed the + * specified SIP Response + * @param response the SIP Response which has been processed by + * sourceCallPeer + * @param request the SIP Request sent by sourceCallPeer + * as a reply to the specified SIP response + */ + public void responseProcessed( + CallPeerSipImpl sourceCallPeer, + Response response, + Request request); +} diff --git a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetBasicTelephonySipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetBasicTelephonySipImpl.java index fd6b58564..a9a1bbabb 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetBasicTelephonySipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetBasicTelephonySipImpl.java @@ -143,7 +143,26 @@ public Call createCall(Contact callee) throws OperationFailedException } /** - * Init and establish the specified call. + * Initializes a new outgoing Call with no peers in it. Intended + * for use by other OperationSets willing to initialize + * Calls and willing to control their establishment in ways + * different than {@link #createOutgoingCall(Address, Message)}. + * + * @return a new outgoing Call with no peers in it + * @throws OperationFailedException if initializing the new outgoing + * Call fails + */ + synchronized CallSipImpl createOutgoingCall() + throws OperationFailedException + { + assertRegistered(); + + return new CallSipImpl(this); + } + + /** + * Initializes and establishes a new outgoing Call to a callee with + * a specific Address. * * @param calleeAddress the address of the callee that we'd like to connect * with. @@ -163,9 +182,7 @@ public Call createCall(Contact callee) throws OperationFailedException private synchronized CallSipImpl createOutgoingCall(Address calleeAddress, javax.sip.message.Message cause) throws OperationFailedException { - assertRegistered(); - - CallSipImpl call = new CallSipImpl(this); + CallSipImpl call = createOutgoingCall(); call.invite(calleeAddress, cause); diff --git a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetTelephonyConferencingSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetTelephonyConferencingSipImpl.java index 8960fdded..e46b48fd9 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetTelephonyConferencingSipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetTelephonyConferencingSipImpl.java @@ -6,47 +6,1458 @@ */ package net.java.sip.communicator.impl.protocol.sip; +import java.io.*; +import java.text.*; +import java.util.*; + +import javax.sip.*; +import javax.sip.address.*; +import javax.sip.header.*; +import javax.sip.message.*; +import javax.sip.message.Message; // disambiguation +import javax.xml.parsers.*; + +import net.java.sip.communicator.impl.protocol.sip.sdp.*; +import net.java.sip.communicator.service.neomedia.*; +import net.java.sip.communicator.service.neomedia.MediaType; // disambiguation 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.util.xml.*; + +import org.w3c.dom.*; +import org.xml.sax.*; /** + * Implements OperationSetTelephonyConferencing for SIP. + * * @author Lubomir Marinov */ public class OperationSetTelephonyConferencingSipImpl - implements OperationSetTelephonyConferencing + implements OperationSetTelephonyConferencing, + CallChangeListener, + CallListener, + MethodProcessorListener, + RegistrationStateChangeListener { /** - * The SIP ProtocolProviderService implementation which created + * The Logger used by the + * OperationSetTelephonyConferencingSipImpl class and its instances + * for logging output. + */ + private static final Logger logger + = Logger.getLogger(OperationSetTelephonyConferencingSipImpl.class); + + /** + * The content sub-type of the content supported in NOTIFY requests handled + * by OperationSetTelephonyConferencingSipImpl. + */ + private static final String CONTENT_SUB_TYPE = "conference-info+xml"; + + /** + * The name of the conference-info XML element + * conference-description. + */ + private static final String ELEMENT_CONFERENCE_DESCRIPTION + = "conference-description"; + + /** + * The name of the conference-info XML element conference-info. + */ + private static final String ELEMENT_CONFERENCE_INFO = "conference-info"; + + /** + * The name of the conference-info XML element conference-state. + */ + private static final String ELEMENT_CONFERENCE_STATE = "conference-state"; + + /** + * The name of the conference-info XML element display-text. + */ + private static final String ELEMENT_DISPLAY_TEXT = "display-text"; + + /** + * The name of the conference-info XML element endpoint. + */ + private static final String ELEMENT_ENDPOINT = "endpoint"; + + /** + * The name of the conference-info XML element media. + */ + private static final String ELEMENT_MEDIA = "media"; + + /** + * The name of the conference-info XML element src-id. + */ + private static final String ELEMENT_SRC_ID = "src-id"; + + /** + * The name of the conference-info XML element status. + */ + private static final String ELEMENT_STATUS = "status"; + + /** + * The name of the conference-info XML element type. + */ + private static final String ELEMENT_TYPE = "type"; + + /** + * The name of the conference-info XML element user. + */ + private static final String ELEMENT_USER = "user"; + + /** + * The name of the conference-info XML element user-count. + */ + private static final String ELEMENT_USER_COUNT = "user-count"; + + /** + * The name of the conference-info XML element users. + */ + private static final String ELEMENT_USERS = "users"; + + /** + * The name of the event package supported by + * OperationSetTelephonyConferencingSipImpl in SUBSCRIBE and NOTIFY + * requests. + */ + private static final String EVENT_PACKAGE = "conference"; + + /** + * The time in seconds before the expiration of a Subscription at + * which the OperationSetTelephonyConferencingSipImpl instance + * managing it should refresh it. + */ + private static final int REFRESH_MARGIN = 60; + + /** + * The time in seconds after which a Subscription should be expired + * by the OperationSetTelephonyConferencingSipImpl instance which + * manages it. + */ + private static final int SUBSCRIPTION_DURATION = 3600; + + /** + * The SIP OperationSetBasicTelephony implementation which this + * instance uses to carry out tasks such as establishing Calls. + */ + private OperationSetBasicTelephonySipImpl basicTelephony; + + /** + * The utility which encodes text so that it's acceptable as the text of an + * XML element or attribute. + */ + private DOMElementWriter domElementWriter = new DOMElementWriter(); + + /** + * The EventPackageNotifier which implements conference + * event-package notifier support on behalf of this + * OperationSetTelephonyConferencing instance. + */ + private final EventPackageNotifier notifier; + + /** + * The SIP ProtocolProviderService implementation which created * this instance and for which telephony conferencing services are being * provided by this instance. */ private final ProtocolProviderServiceSipImpl parentProvider; /** - * Initializes a new OperationSetTelephonyConferencingSipImpl - * instance which is to provide telephony conferencing services for a - * specific SIP ProtocolProviderService implementation. + * The EventPackageNotifier which implements conference + * event-package subscriber support on behalf of this + * OperationSetTelephonyConferencing instance. + */ + private final EventPackageSubscriber subscriber; + + /** + * The Timer which executes delayed tasks scheduled by + * {@link #notifier} and {@link #subscriber}. + */ + private final TimerScheduler timer = new TimerScheduler(); + + /** + * Initializes a new OperationSetTelephonyConferencingSipImpl + * instance which is to provide telephony conferencing services for the + * specified SIP ProtocolProviderService implementation. * - * @param parentProvider - * the SIP ProtocolProviderService which has - * requested the creation of the new instance and for which the - * new instance is to provide telephony conferencing services + * @param parentProvider the SIP ProtocolProviderService + * implementation which has requested the creation of the new instance and + * for which the new instance is to provide telephony conferencing services */ public OperationSetTelephonyConferencingSipImpl( ProtocolProviderServiceSipImpl parentProvider) { this.parentProvider = parentProvider; + + this.subscriber + = new EventPackageSubscriber( + this.parentProvider, + EVENT_PACKAGE, + SUBSCRIPTION_DURATION, + CONTENT_SUB_TYPE, + this.timer, + REFRESH_MARGIN); + this.notifier + = new EventPackageNotifier( + this.parentProvider, + EVENT_PACKAGE, + SUBSCRIPTION_DURATION, + CONTENT_SUB_TYPE, + this.timer) + { + protected Subscription createSubscription( + Address fromAddress, + String eventId) + { + return + new ConferenceNotifierSubscription( + fromAddress, + eventId); + } + }; + + this.parentProvider.addRegistrationStateChangeListener(this); } + /** + * Appends a specific array of Strings to a specific + * StringBuffer. + * + * @param stringBuffer the StringBuffer to append the specified + * strings to + * @param strings the String values to be appended to the specified + * stringBuffer + */ + private static void append(StringBuffer stringBuffer, String... strings) + { + for (String str : strings) + stringBuffer.append(str); + } + + /** + * Notifies this CallListener that a specific Call has + * been established. + * + * @param event a CallEvent which specified the newly-established + * Call + */ + private void callBegun(CallEvent event) + { + Call call = event.getSourceCall(); + + call.addCallChangeListener(this); + + /* + * If there were any CallPeers in the Call prior to our realization that + * it has begun, pretend that they are added afterwards. + */ + Iterator callPeerIter = call.getCallPeers(); + + while (callPeerIter.hasNext()) + callPeerAdded( + new CallPeerEvent( + callPeerIter.next(), + call, + CallPeerEvent.CALL_PEER_ADDED)); + } + + /** + * Notifies this CallListener that a specific Call has + * ended. + * + * @param event a CallEvent which specified the Call which + * has just ended + */ + public void callEnded(CallEvent event) + { + Call call = event.getSourceCall(); + + /* + * If there are still CallPeers after our realization that it has ended, + * pretend that they are removed before that. + */ + Iterator callPeerIter = call.getCallPeers(); + + while (callPeerIter.hasNext()) + callPeerRemoved( + new CallPeerEvent( + callPeerIter.next(), + call, + CallPeerEvent.CALL_PEER_REMOVED)); + + call.removeCallChangeListener(this); + } + + /** + * Notifies this CallChangeListener that a specific + * CallPeer has been added to a specific Call. + * + * @param event a CallPeerEvent which specifies the + * CallPeer which has been added to a Call + */ + public void callPeerAdded(CallPeerEvent event) + { + CallPeerSipImpl callPeer = (CallPeerSipImpl) event.getSourceCallPeer(); + + callPeer.addMethodProcessorListener(this); + + callPeersChanged(event); + } + + /** + * Notifies this CallChangeListener that a specific + * CallPeer has been remove from a specific Call. + * + * @param event a CallPeerEvent which specifies the + * CallPeer which has been removed from a Call + */ + public void callPeerRemoved(CallPeerEvent event) + { + CallPeerSipImpl callPeer = (CallPeerSipImpl) event.getSourceCallPeer(); + + callPeer.removeMethodProcessorListener(this); + + callPeersChanged(event); + } + + /** + * Notifies this CallChangeListener that the CallPeer list + * of a specific Call has been modified by adding or removing a + * specific CallPeer. + * + * @param event a CallPeerEvent which specifies the + * CallPeer which has been added to or removed from a Call + */ + private void callPeersChanged(CallPeerEvent event) + { + notifyAll(SubscriptionStateHeader.ACTIVE, null, event.getSourceCall()); + } + + /** + * Notifies this CallChangeListener that a specific Call + * has changed its state. Does nothing. + * + * @param event a CallChangeEvent which specifies the Call + * which has changed its state, the very state which has been changed and + * the values of the state before and after the change + */ + public void callStateChanged(CallChangeEvent event) + { + } + + /** + * Creates a conference call with the specified callees as call peers. + * + * @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 + * @see OperationSetTelephonyConferencing#createConfCall(String[]) + */ public Call createConfCall(String[] callees) - throws OperationNotSupportedException + throws OperationFailedException + { + int calleeCount = callees.length; + Address[] calleeAddresses = new Address[calleeCount]; + + for (int i = 0; i < calleeCount; i++) + calleeAddresses[i] = parseAddressString(callees[i]); + + CallSipImpl call = basicTelephony.createOutgoingCall(); + + call.setConferenceFocus(true); + + for (Address calleeAddress : calleeAddresses) + inviteCalleeToCall(calleeAddress, call); + return call; + } + + /** + * Generates the conference-info XML to be sent to a specific + * CallPeer in order to notify it of the current state of the + * conference managed by the local peer. + * + * @param callPeer the CallPeer to generate conference-info XML for + * @param version the value of the version attribute of the + * conference-info root element of the conference-info XML to be + * generated + * @return the conference-info XML to be sent to the specified + * callPeer in order to notify it of the current state of the + * conference managed by the local peer + */ + private String getConferenceInfoXML(CallPeerSipImpl callPeer, int version) + { + Dialog dialog = callPeer.getDialog(); + String localParty = null; + + if (dialog != null) + { + Address localPartyAddress = dialog.getLocalParty(); + + if (localPartyAddress != null) + localParty = localPartyAddress.getURI().toString(); + } + + StringBuffer xml = new StringBuffer(); + CallSipImpl call = callPeer.getCall(); + + xml.append( "\r\n"); + // + append(xml, "<", ELEMENT_CONFERENCE_INFO); + // entity + append(xml, " entity=\"", domElementWriter.encode(localParty), "\""); + // state + xml.append(" state=\"full\""); + // version + append(xml, " version=\"", Integer.toString(version), "\">"); + // + append(xml, "<", ELEMENT_CONFERENCE_DESCRIPTION, "/>"); + // + append(xml, "<", ELEMENT_CONFERENCE_STATE, ">"); + // + append(xml, "<", ELEMENT_USER_COUNT, ">"); + xml.append(call.getCallPeerCount() + 1); + // + append(xml, ""); + // + append(xml, ""); + // + append(xml, "<", ELEMENT_USERS, ">"); + + // + append(xml, "<", ELEMENT_USER); + // entity + append(xml, " entity=\"", domElementWriter.encode(localParty), "\""); + // state + xml.append(" state=\"full\">"); + + String ourDisplayName = parentProvider.getOurDisplayName(); + + if (ourDisplayName != null) + { + // + append(xml, "<", ELEMENT_DISPLAY_TEXT, ">"); + xml.append(domElementWriter.encode(ourDisplayName)); + // + append(xml, ""); + } + // + append(xml, "<", ELEMENT_ENDPOINT, ">"); + // + append(xml, "<", ELEMENT_STATUS, ">"); + // We are the conference focus so we're connected to the conference. + xml.append(ConferenceMemberSipImpl.CONNECTED); + // + append(xml, ""); + // + append(xml, "<", ELEMENT_MEDIA, ">"); + // + append(xml, ""); + // + append(xml, ""); + // + append(xml, ""); + + Iterator callPeerIter = call.getCallPeers(); + + while (callPeerIter.hasNext()) + getUserXML(callPeerIter.next(), xml); + + // + append(xml, ""); + // + append(xml, ""); + return xml.toString(); + } + + /** + * Reads the text content of the status XML element of a specific + * endpoint XML element. + * + * @param endpoint an XML Node which represents the + * endpoint XML element from which to get the text content of its + * status XML element + * @return the text content of the status XML element of the + * specified endpoint XML element if any; otherwise, null + */ + private String getEndpointStatus(Node endpoint) + { + NodeList endpointChildList = endpoint.getChildNodes(); + int endpoingChildCount = endpointChildList.getLength(); + + for (int endpointChildIndex = 0; + endpointChildIndex < endpoingChildCount; + endpointChildIndex++) + { + Node endpointChild = endpointChildList.item(endpointChildIndex); + + if (ELEMENT_STATUS.equals(endpointChild.getNodeName())) + return endpointChild.getTextContent(); + } + return null; + } + + /** + * Generates the text content to be put in the status XML element + * of an endpoint XML element and which describes the state of a + * specific CallPeer. + * + * @param callPeer the CallPeer which is to get its state described + * in a status XML element of an endpoint XML element + * @return the text content to be put in the status XML element of + * an endpoint XML element and which describes the state of the + * specified callPeer + */ + private String getEndpointStatusXML(CallPeerSipImpl callPeer) { - throw new OperationNotSupportedException(); + CallPeerState callPeerState = callPeer.getState(); + + if (CallPeerState.ALERTING_REMOTE_SIDE.equals(callPeerState)) + return ConferenceMemberSipImpl.ALERTING; + if (CallPeerState.CONNECTING.equals(callPeerState) + || CallPeerState + .CONNECTING_WITH_EARLY_MEDIA.equals(callPeerState)) + return ConferenceMemberSipImpl.PENDING; + if (CallPeerState.DISCONNECTED.equals(callPeerState)) + return ConferenceMemberSipImpl.DISCONNECTED; + if (CallPeerState.INCOMING_CALL.equals(callPeerState)) + return ConferenceMemberSipImpl.DIALING_IN; + if (CallPeerState.INITIATING_CALL.equals(callPeerState)) + return ConferenceMemberSipImpl.DIALING_OUT; + + /* + * he/she is neither "hearing" the conference mix nor is his/her media + * being mixed in the conference + */ + if (CallPeerState.ON_HOLD_LOCALLY.equals(callPeerState) + || CallPeerState.ON_HOLD_MUTUALLY.equals(callPeerState)) + return ConferenceMemberSipImpl.ON_HOLD; + if (CallPeerState.CONNECTED.equals(callPeerState)) + return ConferenceMemberSipImpl.CONNECTED; + return null; } - public CallPeer inviteCalleeToCall(String uri, Call existingCall) - throws OperationNotSupportedException + /** + * Appends to a specific StringBuffer media XML element + * trees which describe the state of the media streaming between a specific + * CallPeer and its local peer represented by an associated + * Call. + * + * @param callPeer the CallPeer which is to get its media streaming + * state describes in media XML element trees appended to the + * specified StringBuffer + * @param xml the StringBuffer to append the media XML + * trees describing the media streaming state of the specified + * callPeer to + */ + private void getMediaXML(CallPeerSipImpl callPeer, StringBuffer xml) + { + CallPeerMediaHandler mediaHandler = callPeer.getMediaHandler(); + + for (MediaType mediaType : MediaType.values()) + { + MediaStream stream = mediaHandler.getStream(mediaType); + + if (stream != null) + { + // + append(xml, "<", ELEMENT_MEDIA, ">"); + // + append(xml, "<", ELEMENT_TYPE, ">"); + xml.append(mediaType.toString()); + // + append(xml, ""); + + String srcId = stream.getRemoteSourceID(); + + if (srcId != null) + { + // + append(xml, "<", ELEMENT_SRC_ID, ">"); + xml.append(srcId); + // + append(xml, ""); + } + + MediaDirection direction = stream.getDirection(); + + if (direction == null) + direction = MediaDirection.INACTIVE; + // + append(xml, "<", ELEMENT_STATUS, ">"); + xml.append(direction.toString()); + // + append(xml, ""); + // + append(xml, ""); + } + } + } + + /** + * Appends to a specific StringBuffer a user XML element + * tree which describes the participation of a specific CallPeer in + * a conference managed by the local peer represented by its associated + * Call. + * + * @param callPeer the CallPeer which is to get its conference + * participation describes in a user XML element tree appended to + * the specified StringBuffer + * @param xml the StringBuffer to append the user XML + * tree describing the conference participation of the specified + * callPeer to + */ + private void getUserXML(CallPeerSipImpl callPeer, StringBuffer xml) + { + // + append(xml, "<", ELEMENT_USER); + // entity + append( + xml, + " entity=\"", + domElementWriter.encode(callPeer.getAddress()), + "\""); + // state + xml.append(" state=\"full\">"); + + String displayName = callPeer.getDisplayName(); + + if (displayName != null) + { + // + append(xml, "<", ELEMENT_DISPLAY_TEXT, ">"); + xml.append(domElementWriter.encode(displayName)); + // + append(xml, ""); + } + // + append(xml, "<", ELEMENT_ENDPOINT, ">"); + + String status = getEndpointStatusXML(callPeer); + + if (status != null) + { + // + append(xml, "<", ELEMENT_STATUS, ">"); + xml.append(status); + // + append(xml, ""); + } + getMediaXML(callPeer, xml); + // + append(xml, ""); + // + append(xml, ""); + } + + /** + * Notifies this CallListener that a specific incoming + * Call has been received. + * + * @param event a CallEvent which specifies the newly-received + * incoming Call + */ + public void incomingCallReceived(CallEvent event) + { + callBegun(event); + } + + /** + * Invites a callee with a specific SIP Address to be joined in a + * specific Call in the sense of SIP conferencing. + * + * @param calleeAddress the SIP Address of the callee to be invited + * to the specified existing Call + * @param call the existing Call to invite the callee with the + * specified SIP calleeAddress to + * @return a new SIP CallPeer instance which describes the SIP + * signaling and the media streaming of the newly-invited callee within the + * specified Call + * @throws OperationFailedException if inviting the specified callee to the + * specified call fails + */ + private CallPeerSipImpl inviteCalleeToCall( + Address calleeAddress, + CallSipImpl call) + throws OperationFailedException + { + if (!call.isConferenceFocus()) + { + call.setConferenceFocus(true); + + Iterator callPeerIter = call.getCallPeers(); + + while (callPeerIter.hasNext()) + callPeerIter.next().sendReInvite(); + } + + return call.invite(calleeAddress, null); + } + + /** + * Invites the callee represented by the specified uri to an already + * existing call. The difference between this method and createConfCall is + * that inviteCalleeToCall allows a user to transform an existing 1 to 1 + * call into a conference call, or 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 + */ + public CallPeer inviteCalleeToCall(String uri, Call call) + throws OperationFailedException + { + return inviteCalleeToCall(parseAddressString(uri), (CallSipImpl) call); + } + + /** + * Notifies this MethodProcessorListener that the procedure for + * handling an INVITE or reINVITE SIP Request has completed and it + * is appropriate to determine whether the remote CallPeer is a + * conference focus. + * + * @param sourceCallPeer the CallPeer with which the procedure for + * handling an INVITE or reINVITE SIP Request has finished + * negotiating + * @param remoteMessage the remote SIP Message which was received, + * processed and which prompted sending a specific local SIP + * Message + * @param localMessage the local SIP Message which was sent to the + * CallPeer as part of the processing of its remote SIP + * Message + */ + private void inviteCompleted( + CallPeerSipImpl sourceCallPeer, + Message remoteMessage, + Message localMessage) + { + ContactHeader contactHeader + = (ContactHeader) remoteMessage.getHeader(ContactHeader.NAME); + boolean conferenceFocus = false; + + if (contactHeader != null) + { + /* + * The javadoc says that ContactHeader#getParameter(String) will + * return an empty string for a flag but it does not and returns + * null. + */ + Iterator parameterNameIter + = contactHeader.getParameterNames(); + + while (parameterNameIter.hasNext()) + if ("isfocus" + .equalsIgnoreCase(parameterNameIter.next().toString())) + { + conferenceFocus = true; + break; + } + } + + sourceCallPeer.setConferenceFocus(conferenceFocus); + if (conferenceFocus) + { + ConferenceSubscriberSubscription subscription + = new ConferenceSubscriberSubscription(sourceCallPeer); + + try + { + subscriber.subscribe(subscription); + } + catch (OperationFailedException ofe) + { + logger + .error( + "Failed to create or send a conference subscription to " + + sourceCallPeer, + ofe); + } + } + } + + /** + * Notifies this CallListener that a specific outgoing + * Call has been created. + * + * @param event a CallEvent which specifies the newly-created + * outgoing Call + */ + public void outgoingCallCreated(CallEvent event) + { + callBegun(event); + } + + /** + * Parses a String value which represents a SIP address into a SIP + * Address value. + * + * @param calleeAddressString a String value which represents a SIP + * address to be parsed into a SIP Address value + * @return a SIP Address value which represents the specified + * calleeAddressString + * @throws OperationFailedException if parsing the specified + * calleeAddressString fails + */ + private Address parseAddressString(String calleeAddressString) + throws OperationFailedException + { + try + { + return parentProvider.parseAddressString(calleeAddressString); + } + catch (ParseException pe) + { + ProtocolProviderServiceSipImpl + .throwOperationFailedException( + "Failed to parse callee address " + calleeAddressString, + OperationFailedException.ILLEGAL_ARGUMENT, + pe, + logger); + return null; + } + } + + /** + * Notifies this RegistrationStateChangeListener that the + * ProtocolProviderSerivce it is registered with has changed its + * registration state. + * + * @param event a RegistrationStateChangeEvent which specifies the + * old and the new value of the registration state of the + * ProtocolProviderService this + * RegistrationStateChangeListener listens to + */ + public void registrationStateChanged(RegistrationStateChangeEvent event) + { + RegistrationState newState = event.getNewState(); + + if (RegistrationState.REGISTERED.equals(newState)) + { + OperationSetBasicTelephony basicTelephony + = parentProvider + .getOperationSet(OperationSetBasicTelephony.class); + + if (this.basicTelephony != basicTelephony) + { + this.basicTelephony + = (OperationSetBasicTelephonySipImpl) basicTelephony; + this.basicTelephony.addCallListener(this); + } + } + else if (RegistrationState.UNREGISTERED.equals(newState)) + { + if (basicTelephony != null) + { + basicTelephony.removeCallListener(this); + basicTelephony = null; + } + } + } + + /** + * Notifies this MethodProcessorListener that a specific + * CallPeer has processed a specific SIP Request and has + * replied to it with a specific SIP Response. + * + * @param sourceCallPeer the CallPeer which has processed the + * specified SIP Request + * @param request the SIP Request which has been processed by + * sourceCallPeer + * @param response the SIP Response sent by sourceCallPeer + * as a reply to the specified SIP request + * @see MethodProcessorListener#requestProcessed(CallPeerSipImpl, Request, + * Response) + */ + public void requestProcessed( + CallPeerSipImpl sourceCallPeer, + Request request, + Response response) { - throw new OperationNotSupportedException(); + if (Request.INVITE.equalsIgnoreCase(request.getMethod()) + && (response != null) + && (Response.OK == response.getStatusCode())) + inviteCompleted(sourceCallPeer, request, response); + } + + /** + * Notifies this MethodProcessorListener that a specific + * CallPeer has processed a specific SIP Response and has + * replied to it with a specific SIP Request. + * + * @param sourceCallPeer the CallPeer which has processed the + * specified SIP Response + * @param response the SIP Response which has been processed by + * sourceCallPeer + * @param request the SIP Request sent by sourceCallPeer + * as a reply to the specified SIP response + * @see MethodProcessorListener#responseProcessed(CallPeerSipImpl, Response, + * Request) + */ + public void responseProcessed( + CallPeerSipImpl sourceCallPeer, + Response response, + Request request) + { + if (Response.OK == response.getStatusCode()) + { + CSeqHeader cseqHeader + = (CSeqHeader) response.getHeader(CSeqHeader.NAME); + + if ((cseqHeader != null) + && Request.INVITE.equalsIgnoreCase(cseqHeader.getMethod())) + inviteCompleted(sourceCallPeer, response, request); + } + } + + /** + * Updates the conference-related properties of a specific CallPeer + * such as conferenceFocus and conferenceMembers with + * information received from it as a conference focus in the form of a + * conference-info XML document. + * + * @param callPeer the CallPeer which is a conference focus and has + * sent the specified conference-info XML document + * @param conferenceInfoDocument the conference-info XML document sent by + * callPeer in order to update the conference-related information + * of the local peer represented by the associated Call + */ + private void setConferenceInfoDocument( + CallPeerSipImpl callPeer, + Document conferenceInfoDocument) + { + NodeList usersList + = conferenceInfoDocument.getElementsByTagName(ELEMENT_USERS); + ConferenceMember[] conferenceMembersToRemove + = callPeer.getConferenceMembers(); + int conferenceMembersToRemoveCount = conferenceMembersToRemove.length; + + if (usersList.getLength() > 0) + { + NodeList userList = usersList.item(0).getChildNodes(); + int userCount = userList.getLength(); + + for (int userIndex = 0; userIndex < userCount; userIndex++) + { + Node user = userList.item(userIndex); + + if (!ELEMENT_USER.equals(user.getNodeName())) + continue; + + String address = ((Element) user).getAttribute("entity"); + + if ((address == null) || (address.length() < 1)) + continue; + + /* + * Determine the ConferenceMembers which are no longer in the + * list. + */ + ConferenceMemberSipImpl existingConferenceMember = null; + + for (int conferenceMemberIndex = 0; + conferenceMemberIndex < conferenceMembersToRemoveCount; + conferenceMemberIndex++) + { + ConferenceMemberSipImpl conferenceMember + = (ConferenceMemberSipImpl) + conferenceMembersToRemove[conferenceMemberIndex]; + + if ((conferenceMember != null) + && address + .equalsIgnoreCase( + conferenceMember.getAddress())) + { + conferenceMembersToRemove[conferenceMemberIndex] = null; + existingConferenceMember = conferenceMember; + break; + } + } + + // Create the new ones. + boolean addConferenceMember; + if (existingConferenceMember == null) + { + existingConferenceMember + = new ConferenceMemberSipImpl(callPeer, address); + addConferenceMember = true; + } + else + addConferenceMember = false; + + // Update the existing ones. + if (existingConferenceMember != null) + { + NodeList userChildList = user.getChildNodes(); + int userChildCount = userChildList.getLength(); + String displayName = null; + String endpointStatus = null; + + for (int userChildIndex = 0; + userChildIndex < userChildCount; + userChildIndex++) + { + Node userChild = userChildList.item(userChildIndex); + String userChildName = userChild.getNodeName(); + + if (ELEMENT_DISPLAY_TEXT.equals(userChildName)) + { + displayName = userChild.getTextContent(); + } else if (ELEMENT_ENDPOINT.equals(userChildName)) + { + endpointStatus = getEndpointStatus(userChild); + } + } + existingConferenceMember.setDisplayName(displayName); + existingConferenceMember.setEndpointStatus(endpointStatus); + + if (addConferenceMember) + callPeer.addConferenceMember(existingConferenceMember); + } + } + } + + /* + * Remove the ConferenceMember instance which are no longer present in + * the conference-info XML document. + */ + for (int conferenceMemberIndex = 0; + conferenceMemberIndex < conferenceMembersToRemoveCount; + conferenceMemberIndex++) + { + ConferenceMember conferenceMemberToRemove + = conferenceMembersToRemove[conferenceMemberIndex]; + + if (conferenceMemberToRemove != null) + callPeer.removeConferenceMember(conferenceMemberToRemove); + } + } + + /** + * Updates the conference-related properties of a specific CallPeer + * such as conferenceFocus and conferenceMembers with + * information received from it as a conference focus in the form of a + * conference-info XML document. + * + * @param callPeer the CallPeer which is a conference focus and has + * sent the specified conference-info XML document + * @param conferenceInfoXML the conference-info XML document sent by + * callPeer in order to update the conference-related information + * of the local peer represented by the associated Call + */ + private void setConferenceInfoXML( + CallPeerSipImpl callPeer, + String conferenceInfoXML) + { + byte[] bytes; + + try + { + bytes = conferenceInfoXML.getBytes("UTF-8"); + } + catch (UnsupportedEncodingException uee) + { + logger + .warn( + "Failed to gets bytes from String for the UTF-8 charset", + uee); + bytes = conferenceInfoXML.getBytes(); + } + + Document document = null; + Throwable exception = null; + + try + { + document + = DocumentBuilderFactory + .newInstance() + .newDocumentBuilder() + .parse(new ByteArrayInputStream(bytes)); + } + catch (IOException ioe) + { + exception = ioe; + } + catch (ParserConfigurationException pce) + { + exception = pce; + } + catch (SAXException saxe) + { + exception = saxe; + } + if (exception != null) + logger.error("Failed to parse conference-info XML", exception); + else + { + /* + * The CallPeer sent conference-info XML so we're sure it's a + * conference focus. + */ + callPeer.setConferenceFocus(true); + setConferenceInfoDocument(callPeer, document); + } + } + + /** + * Notifies all Subscriptions associated with and established in a + * specific Call about a specific subscription state and the reason + * for that subscription state. + * + * @param subscriptionState the subscription state to notify about + * @param reason the reason for entering the specified + * subscriptionState + * @param call the Call in which the Subscriptions to be + * notified have been established + */ + private void notifyAll( + String subscriptionState, + String reason, + final Call call) + { + EventPackageNotifier.SubscriptionFilter subscriptionFilter + = new EventPackageNotifier.SubscriptionFilter() + { + public boolean accept( + EventPackageNotifier.Subscription subscription) + { + return + (subscription instanceof ConferenceNotifierSubscription) + && call + .equals( + ((ConferenceNotifierSubscription) + subscription) + .getCall()); + } + }; + + try + { + notifier.notifyAll(subscriptionState, reason, subscriptionFilter); + } + catch (OperationFailedException ofe) + { + logger + .error( + "Failed to notify the conference subscriptions of " + call, + ofe); + } + } + + /** + * Implements EventPackageNotifier.Subscription in order to + * represent a conference subscription created by a remote CallPeer + * to the conference event package of a local Call. + */ + private class ConferenceNotifierSubscription + extends EventPackageNotifier.Subscription + { + + /** + * The value of the version attribute to be specified in the + * outgoing conference-info root XML elements. + */ + private int version = 1; + + /** + * Initializes a new ConferenceNotifierSubscription instance + * with a specific subscription Address/Request URI and a + * specific id tag of the associated Event headers. + * + * @param fromAddress the subscription Address/Request URI + * which is to be the target of the NOTIFY requests associated with the + * new instance + * @param eventId the value of the id tag to be placed in the Event + * headers of the NOTIFY requests created for the new instance and to be + * present in the received Event headers in order to have the new + * instance associated with them + */ + public ConferenceNotifierSubscription( + Address fromAddress, + String eventId) + { + super(fromAddress, eventId); + } + + /** + * Creates the content of the NOTIFY request to be sent to the target + * represented by this Subscription and having a specific + * subscription state and a specific reason for that subscription state. + * + * @param subscriptionState the subscription state to be notified about + * in the NOTIFY request which is to carry the returned content + * @param reason the reason for the subscription state to be notified + * about in the NOTIFY request which is to carry the returned content + * + * @return an array of bytes representing the content of the + * NOTIFY request to be sent to the target represented by this + * Subscription + * @see EventPackageNotifier.Subscription#createNotifyContent(String, + * String) + */ + protected byte[] createNotifyContent( + String subscriptionState, + String reason) + { + CallPeerSipImpl callPeer = getCallPeer(); + + if (callPeer == null) + { + logger + .error( + "Failed to find the CallPeer of the conference subscription " + + this); + return null; + } + + String conferenceInfoXML = getConferenceInfoXML(callPeer, version); + byte[] notifyContent; + + if (conferenceInfoXML == null) + notifyContent = null; + else + { + try + { + notifyContent = conferenceInfoXML.getBytes("UTF-8"); + } + catch (UnsupportedEncodingException uee) + { + logger + .warn( + "Failed to gets bytes from String for the UTF-8 charset", + uee); + notifyContent = conferenceInfoXML.getBytes(); + } + ++ version; + } + return notifyContent; + } + + /** + * Gets the Call of the CallPeerSipImpl subscribed to + * the EventPackageNotifier and represented by this + * Subscription. + * + * @return the Call of the CallPeerSipImpl subscribed + * to the EventPackageNotifier and represented by this + * Subscription + */ + public CallSipImpl getCall() + { + CallPeerSipImpl callPeer = getCallPeer(); + + return (callPeer == null) ? null : callPeer.getCall(); + } + + /** + * Gets the CallPeerSipImpl subscribed to the + * EventPackageNotifier and represented by this + * Subscription. + * + * @return the CallPeerSipImpl subscribed to the + * EventPackageNotifier and represented by this + * Subscription + */ + private CallPeerSipImpl getCallPeer() + { + Dialog dialog = getDialog(); + + if (dialog != null) + { + OperationSetBasicTelephonySipImpl basicTelephony + = OperationSetTelephonyConferencingSipImpl.this.basicTelephony; + + if (basicTelephony != null) + return + basicTelephony + .getActiveCallsRepository().findCallPeer(dialog); + } + return null; + } + } + + /** + * Implements EventPackageSubscriber.Subscription in order to + * represent the conference subscription of the local peer to the conference + * event package of a specific remote CallPeer acting as a + * conference focus. + */ + private class ConferenceSubscriberSubscription + extends EventPackageSubscriber.Subscription + { + + /** + * The CallPeer which is acting as a conference focus in its + * Call with the local peer. + */ + private final CallPeerSipImpl callPeer; + + /** + * Initializes a new ConferenceSubscriberSubscription instance + * which is to represent the conference subscription of the local peer + * to the conference event package of a specific CallPeer + * acting as a conference focus. + * + * @param callPeer the CallPeer acting as a conference focus + * which the new instance is to subscribe to + */ + public ConferenceSubscriberSubscription(CallPeerSipImpl callPeer) + { + super(callPeer.getPeerAddress()); + + this.callPeer = callPeer; + } + + /** + * Gets the Dialog which was created by the SUBSCRIBE request + * associated with this Subscription or which was used to send + * that request in. + * + * @return the Dialog which was created by the SUBSCRIBE + * request associated with this Subscription or which was used + * to send that request in; null if the success of the + * SUBSCRIBE request has not been confirmed yet or this + * Subscription was removed from the list of the + * EventPackageSupport it used to be in + * @see EventPackageSubscriber.Subscription#getDialog() + */ + @Override + protected Dialog getDialog() + { + Dialog dialog = super.getDialog(); + + if ((dialog == null) + || DialogState.TERMINATED.equals(dialog.getState())) + dialog = callPeer.getDialog(); + return dialog; + } + + /** + * Notifies this Subscription that an active NOTIFY + * Request has been received and it may process the specified + * raw content carried in it. + * + * @param requestEvent the RequestEvent carrying the full + * details of the received NOTIFY Request including the raw + * content which may be processed by this Subscription + * @param rawContent an array of bytes which represents the raw content + * carried in the body of the received NOTIFY Request and + * extracted from the specified RequestEvent for the + * convenience of the implementers + * @see EventPackageSubscriber.Subscription#processActiveRequest( + * RequestEvent, byte[]) + */ + protected void processActiveRequest( + RequestEvent requestEvent, + byte[] rawContent) + { + if (rawContent != null) + setConferenceInfoXML( + callPeer, + SdpUtils.getContentAsString(requestEvent.getRequest())); + } + + /** + * Notifies this Subscription that a Response to a + * previous SUBSCRIBE Request has been received with a status + * code in the failure range and it may process the status code carried + * in it. + * + * @param responseEvent the ResponseEvent carrying the full + * details of the received Response including the status code + * which may be processed by this Subscription + * @param statusCode the status code carried in the Response + * and extracted from the specified ResponseEvent for the + * convenience of the implementers + * @see EventPackageSubscriber.Subscription#processFailureResponse( + * ResponseEvent, int) + */ + protected void processFailureResponse( + ResponseEvent responseEvent , + int statusCode) + { + + /* + * We've failed to subscribe to the conference event package of the + * CallPeer so, regardless of it announcing "isfocus", it's as good + * as not being a conference focus because we'll not be able to + * retrieve its ConferenceMembers. + */ + callPeer.setConferenceFocus(false); + } + + /** + * Notifies this Subscription that a Response to a + * previous SUBSCRIBE Request has been received with a status + * code in the success range and it may process the status code carried + * in it. + * + * @param responseEvent the ResponseEvent carrying the full + * details of the received Response including the status code + * which may be processed by this Subscription + * @param statusCode the status code carried in the Response + * and extracted from the specified ResponseEvent for the + * convenience of the implementers + * @see EventPackageSubscriber.Subscription#processSuccessResponse( + * ResponseEvent, int) + */ + protected void processSuccessResponse( + ResponseEvent responseEvent, + int statusCode) + { + switch (statusCode) + { + case Response.OK: + case Response.ACCEPTED: + + /* + * We've managed to subscribe to the conference event package of + * the CallPeer so, regardless of it not announcing "isfocus", + * we know it's a conference focus. + */ + callPeer.setConferenceFocus(true); + break; + } + } + + /** + * Notifies this Subscription that a terminating NOTIFY + * Request has been received and it may process the reason code + * carried in it. + * + * @param requestEvent the RequestEvent carrying the full + * details of the received NOTIFY Request including the reason + * code which may be processed by this Subscription + * @param reasonCode the code of the reason for the termination carried + * in the NOTIFY Request and extracted from the specified + * RequestEvent for the convenience of the implementers + * @see EventPackageSubscriber.Subscription#processTerminatedRequest( + * RequestEvent, String) + */ + protected void processTerminatedRequest( + RequestEvent requestEvent, + String reasonCode) + { + if (SubscriptionStateHeader.DEACTIVATED.equals(reasonCode)) + try + { + subscriber.poll(this); + } + catch (OperationFailedException ofe) + { + logger + .error( + "Failed to renew the conference subscription " + + this, + ofe); + } + } } } diff --git a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetVideoTelephonySipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetVideoTelephonySipImpl.java index d45f6dfed..5e959c16b 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetVideoTelephonySipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetVideoTelephonySipImpl.java @@ -74,22 +74,20 @@ public void addVideoListener(CallPeer peer, VideoListener listener) } /** - * Implements OperationSetVideoTelephony#createLocalVisualComponent( - * CallPeer, VideoListener). Delegates to - * CallSession#createLocalVisualComponent(VideoListener) of the Call of the - * specified CallPeer because the CallSession manages the visual components - * which represent local video. + * Implements + * {@link OperationSetVideoTelephony#createLocalVisualComponent(CallPeer, + * VideoListener)}. * * @param peer the CallPeer that we are sending our local video to. * @param listener the VideoListener where we'd like to retrieve * the Component containing the local video. - * * @return the Component containing the local video. - * * @throws OperationFailedException if we fail extracting the local video. */ - public Component createLocalVisualComponent(CallPeer peer, - VideoListener listener) throws OperationFailedException + public Component createLocalVisualComponent( + CallPeer peer, + VideoListener listener) + throws OperationFailedException { /** * @todo update to neomedia. @@ -114,10 +112,9 @@ public Component createLocalVisualComponent(CallPeer peer, } /** - * Implements OperationSetVideoTelephony#disposeLocalVisualComponent( - * CallPeer, Component). Delegates to CallSession#disposeLocalVisualComponent( - * Component) of the Call of the specified CallPeer because the - * CallSession manages the visual components which represent local video. + * Implements + * {@link OperationSetVideoTelephony#disposeLocalVisualComponent(CallPeer, + * Component)}. * * @param peer the CallPeer whose local video component we'd like * to dispose of. diff --git a/src/net/java/sip/communicator/impl/protocol/sip/sip.provider.manifest.mf b/src/net/java/sip/communicator/impl/protocol/sip/sip.provider.manifest.mf index 10f0733a0..6e3263d87 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/sip.provider.manifest.mf +++ b/src/net/java/sip/communicator/impl/protocol/sip/sip.provider.manifest.mf @@ -7,6 +7,7 @@ System-Bundle: yes Import-Package: org.apache.log4j, org.osgi.framework, org.w3c.dom, + org.xml.sax, net.java.sip.communicator.service.argdelegation, net.java.sip.communicator.service.configuration, net.java.sip.communicator.service.configuration.event, diff --git a/src/net/java/sip/communicator/service/protocol/AbstractCallPeer.java b/src/net/java/sip/communicator/service/protocol/AbstractCallPeer.java index 7c43e440e..4bed1e3fa 100644 --- a/src/net/java/sip/communicator/service/protocol/AbstractCallPeer.java +++ b/src/net/java/sip/communicator/service/protocol/AbstractCallPeer.java @@ -13,9 +13,9 @@ import net.java.sip.communicator.util.*; /** - * Provides a default implementation for most of the - * CallPeer methods with the purpose of only leaving custom - * protocol development to clients using the PhoneUI service. + * Provides a default implementation for most of the CallPeer methods + * with the purpose of only leaving custom protocol development to clients using + * the PhoneUI service. * * @author Emil Ivov * @author Lubomir Marinov @@ -32,9 +32,8 @@ public abstract class AbstractCallPeer = Logger.getLogger(AbstractCallPeer.class); /** - * The constant which describes an empty set of - * ConferenceMembers (and which can be used to reduce - * allocations). + * The constant which describes an empty set of ConferenceMembers + * (and which can be used to reduce allocations). */ protected static final ConferenceMember[] NO_CONFERENCE_MEMBERS = new ConferenceMember[0]; @@ -46,8 +45,7 @@ public abstract class AbstractCallPeer = new ArrayList(); /** - * All the CallPeerSecurityListener-s registered with this - * CallPeer. + * All the CallPeerSecurityListener-s registered with this CallPeer. */ protected final List callPeerSecurityListeners @@ -71,23 +69,23 @@ public abstract class AbstractCallPeer /** * The indicator which determines whether this peer is acting as a * conference focus and thus may provide information about - * ConferenceMember such as {@link #getConferenceMembers()} and + * ConferenceMember such as {@link #getConferenceMembers()} and * {@link #getConferenceMemberCount()}. */ private boolean conferenceFocus; /** - * The list of ConferenceMembers currently known to and managed - * in a conference by this peer. + * The list of ConferenceMembers currently known to and managed in + * a conference by this peer. */ private final List conferenceMembers = new ArrayList(); /** - * The list of CallPeerConferenceListeners interested in - * and to be notified about changes in conference-related information such - * as this peer acting or not acting as a conference focus and - * conference membership details. + * The list of CallPeerConferenceListeners interested in and to be + * notified about changes in conference-related information such as this + * peer acting or not acting as a conference focus and conference membership + * details. */ protected final List callPeerConferenceListeners @@ -111,7 +109,8 @@ public abstract class AbstractCallPeer /** * Registers the listener to the list of listeners that would be - * receiving CallPeerEvents + * receiving CallPeerEvents. + * * @param listener a listener instance to register with this peer. */ public void addCallPeerListener(CallPeerListener listener) @@ -127,6 +126,7 @@ public void addCallPeerListener(CallPeerListener listener) /** * Unregisters the specified listener. + * * @param listener the listener to unregister. */ public void removeCallPeerListener(CallPeerListener listener) @@ -141,7 +141,7 @@ public void removeCallPeerListener(CallPeerListener listener) /** * Registers the listener to the list of listeners that would be - * receiving CallPeerSecurityEvents + * receiving CallPeerSecurityEvents. * * @param listener a listener instance to register with this peer. */ @@ -174,9 +174,9 @@ public void removeCallPeerSecurityListener( } /** - * Constructs a CallPeerChangeEvent using this call - * peer as source, setting it to be of type eventType and - * the corresponding oldValue and newValue, + * Constructs a CallPeerChangeEvent using this call peer as source, + * setting it to be of type eventType and the corresponding + * oldValue and newValue, * * @param eventType the type of the event to create and dispatch. * @param oldValue the value of the source property before it changed. @@ -191,9 +191,9 @@ protected void fireCallPeerChangeEvent(String eventType, /** - * Constructs a CallPeerChangeEvent using this call - * peer as source, setting it to be of type eventType and - * the corresponding oldValue and newValue, + * Constructs a CallPeerChangeEvent using this call peer as source, + * setting it to be of type eventType and the corresponding + * oldValue and newValue. * * @param eventType the type of the event to create and dispatch. * @param oldValue the value of the source property before it changed. @@ -246,9 +246,9 @@ protected void fireCallPeerChangeEvent(String eventType, } /** - * Constructs a CallPeerSecurityStatusEvent using this call - * peer as source, setting it to be of type eventType and - * the corresponding oldValue and newValue + * Constructs a CallPeerSecurityStatusEvent using this call peer as + * source, setting it to be of type eventType and the corresponding + * oldValue and newValue. * * @param sessionType the type of the session - audio or video * @param cipher the cipher associated with the event. @@ -283,9 +283,9 @@ protected void fireCallPeerSecurityOnEvent(int sessionType, String cipher, } /** - * Constructs a CallPeerSecurityStatusEvent using this call - * peer as source, setting it to be of type eventType and - * the corresponding oldValue and newValue, + * Constructs a CallPeerSecurityStatusEvent using this call peer as + * source, setting it to be of type eventType and the corresponding + * oldValue and newValue. * * @param sessionType the type of the session - audio or video */ @@ -315,9 +315,9 @@ protected void fireCallPeerSecurityOffEvent(int sessionType) } /** - * Constructs a CallPeerSecurityStatusEvent using this call - * peer as source, setting it to be of type eventType and - * the corresponding oldValue and newValue, + * Constructs a CallPeerSecurityStatusEvent using this call peer as + * source, setting it to be of type eventType and the corresponding + * oldValue and newValue. * * @param messageType the type of the message * @param i18nMessage message @@ -357,8 +357,10 @@ protected void fireCallPeerSecurityMessageEvent( * Returns a string representation of the peer in the form of *
* Display Name <address>;status=CallPeerStatus + * * @return a string representation of the peer and its state. */ + @Override public String toString() { return getDisplayName() + " <" + getAddress() @@ -367,12 +369,12 @@ public String toString() /** * Returns a URL pointing ta a location with call control information for - * this peer or null if no such URL is available for this - * call peer. + * this peer or null if no such URL is available for this call + * peer. * * @return a URL link to a location with call information or a call control - * web interface related to this peer or null if no such URL - * is available. + * web interface related to this peer or null if no such URL is + * available. */ public URL getCallInfoURL() { @@ -392,9 +394,8 @@ public CallPeerState getState() } /** - * Causes this CallPeer to enter the specified state. The method also - * sets the currentStateStartDate field and fires a - * CallPeerChangeEvent. + * Causes this CallPeer to enter the specified state. The method also sets + * the currentStateStartDate field and fires a CallPeerChangeEvent. * * @param newState the state this call peer should enter. * @param reason a string that could be set to contain a human readable @@ -423,9 +424,8 @@ public void setState(CallPeerState newState, String reason) } /** - * Causes this CallPeer to enter the specified state. The method also - * sets the currentStateStartDate field and fires a - * CallPeerChangeEvent. + * Causes this CallPeer to enter the specified state. The method also sets + * the currentStateStartDate field and fires a CallPeerChangeEvent. * * @param newState the state this call peer should enter. */ @@ -435,15 +435,14 @@ public void setState(CallPeerState newState) } /** - * Gets the time at which this CallPeer transitioned - * into a state (likely {@link CallPeerState#CONNECTED}) marking the - * start of the duration of the participation in a Call. + * Gets the time at which this CallPeer transitioned into a state + * (likely {@link CallPeerState#CONNECTED}) marking the start of the + * duration of the participation in a Call. * - * @return the time at which this CallPeer transitioned - * into a state marking the start of the duration of the - * participation in a Call or - * {@link CallPeer#CALL_DURATION_START_TIME_UNKNOWN} if such - * a transition has not been performed + * @return the time at which this CallPeer transitioned into a + * state marking the start of the duration of the participation in a + * Call or {@link CallPeer#CALL_DURATION_START_TIME_UNKNOWN} if + * such a transition has not been performed */ public long getCallDurationStartTime() { @@ -451,14 +450,14 @@ public long getCallDurationStartTime() } /** - * Determines whether the audio stream (if any) being sent to this - * peer is mute. + * Determines whether the audio stream (if any) being sent to this peer is + * mute. *

* The default implementation returns false. *

* - * @return true if an audio stream is being sent to this - * peer and it is currently mute; false, otherwise + * @return true if an audio stream is being sent to this peer and + * it is currently mute; false, otherwise */ public boolean isMute() { @@ -508,6 +507,7 @@ public void setConferenceFocus(boolean conferenceFocus) * Implements CallPeer#getConferenceMembers(). In order to reduce * allocations, returns #NO_CONFERENCE_MEMBERS if #conferenceMembers * contains no ConferenceMember instances. + * * @return an array of the conference members */ public ConferenceMember[] getConferenceMembers() @@ -533,6 +533,7 @@ public ConferenceMember[] getConferenceMembers() * Returns the count of the members contained in this peer. *

* Implements CallPeer#getConferenceMemberCount(). + * * @return the count of the members contained in this peer */ public int getConferenceMemberCount() @@ -541,19 +542,17 @@ public int getConferenceMemberCount() } /** - * Adds a specific ConferenceMember to the list of - * ConferenceMembers reported by this peer through + * Adds a specific ConferenceMember to the list of + * ConferenceMembers reported by this peer through * {@link #getConferenceMembers()} and {@link #getConferenceMemberCount()} * and fires - * CallPeerConferenceEvent#CONFERENCE_MEMBER_ADDED to - * the currently registered CallPeerConferenceListeners. + * CallPeerConferenceEvent#CONFERENCE_MEMBER_ADDED to + * the currently registered CallPeerConferenceListeners. * - * @param conferenceMember - * a ConferenceMember to be added to the list of - * ConferenceMember reported by this peer. If - * the specified ConferenceMember is already - * contained in the list, it is not added again and no event is - * fired. + * @param conferenceMember a ConferenceMember to be added to the + * list of ConferenceMember reported by this peer. If the specified + * ConferenceMember is already contained in the list, it is not + * added again and no event is fired. */ public void addConferenceMember(ConferenceMember conferenceMember) { @@ -573,18 +572,17 @@ public void addConferenceMember(ConferenceMember conferenceMember) } /** - * Removes a specific ConferenceMember from the list of - * ConferenceMembers reported by this peer through + * Removes a specific ConferenceMember from the list of + * ConferenceMembers reported by this peer through * {@link #getConferenceMembers()} and {@link #getConferenceMemberCount()} * if it is contained and fires - * CallPeerConferenceEvent#CONFERENCE_MEMBER_REMOVED to - * the currently registered CallPeerConferenceListeners. + * CallPeerConferenceEvent#CONFERENCE_MEMBER_REMOVED to + * the currently registered CallPeerConferenceListeners. * - * @param conferenceMember - * a ConferenceMember to be removed from the list of - * ConferenceMember reported by this peer. If - * the specified ConferenceMember is no contained in - * the list, no event is fired. + * @param conferenceMember a ConferenceMember to be removed from + * the list of ConferenceMember reported by this peer. If the + * specified ConferenceMember is no contained in the list, no event + * is fired. */ public void removeConferenceMember(ConferenceMember conferenceMember) { @@ -603,10 +601,12 @@ public void removeConferenceMember(ConferenceMember conferenceMember) } /** - * ImplementsCallPeer#addCallPeerConferenceListener( - * CallPeerConferenceListener). In the fashion of the addition of the - * other listeners, does not throw an exception on attempting to add a null - * listeners and just ignores the call. + * Implements + * CallPeer#addCallPeerConferenceListener( + * CallPeerConferenceListener). In the fashion of the addition of the + * other listeners, does not throw an exception on attempting to add a + * null listeners and just ignores the call. + * * @param listener the CallPeerConferenceListener to add */ public void addCallPeerConferenceListener( @@ -621,8 +621,10 @@ public void addCallPeerConferenceListener( } /** - * Implements CallPeer#removeCallPeerConferenceListener( - * CallPeerConferenceListener). + * Implements + * CallPeer#removeCallPeerConferenceListener( + * CallPeerConferenceListener). + * * @param listener the CallPeerConferenceListener to remove */ public void removeCallPeerConferenceListener( @@ -704,13 +706,12 @@ public void removeConferenceMembersSoundLevelListener( } /** - * Fires a specific CallPeerConferenceEvent to the - * CallPeerConferenceListeners interested in changes in - * the conference-related information provided by this peer. + * Fires a specific CallPeerConferenceEvent to the + * CallPeerConferenceListeners interested in changes in the + * conference-related information provided by this peer. * - * @param conferenceEvent - * a CallPeerConferenceEvent to be fired and - * carrying the event data + * @param conferenceEvent a CallPeerConferenceEvent to be fired and + * carrying the event data */ protected void fireCallPeerConferenceEvent( CallPeerConferenceEvent conferenceEvent) @@ -728,6 +729,34 @@ protected void fireCallPeerConferenceEvent( int eventID = conferenceEvent.getEventID(); + if (logger.isDebugEnabled()) + { + String eventIDString; + + switch (eventID) + { + case CallPeerConferenceEvent.CONFERENCE_FOCUS_CHANGED: + eventIDString = "CONFERENCE_FOCUS_CHANGED"; + break; + case CallPeerConferenceEvent.CONFERENCE_MEMBER_ADDED: + eventIDString = "CONFERENCE_MEMBER_ADDED"; + break; + case CallPeerConferenceEvent.CONFERENCE_MEMBER_REMOVED: + eventIDString = "CONFERENCE_MEMBER_REMOVED"; + break; + default: + eventIDString = "UNKNOWN"; + break; + } + logger + .debug( + "Firing CallPeerConferenceEvent with ID " + + eventIDString + + " to " + + listeners.length + + " listeners"); + } + for (CallPeerConferenceListener listener : listeners) switch (eventID) { diff --git a/src/net/java/sip/communicator/service/protocol/OperationSetTelephonyConferencing.java b/src/net/java/sip/communicator/service/protocol/OperationSetTelephonyConferencing.java index 61a79345c..42cf63aee 100644 --- a/src/net/java/sip/communicator/service/protocol/OperationSetTelephonyConferencing.java +++ b/src/net/java/sip/communicator/service/protocol/OperationSetTelephonyConferencing.java @@ -10,40 +10,42 @@ * Provides operations necessary to create and handle conferencing calls. * * @author Emil Ivov + * @author Lubomir Marinov */ public interface OperationSetTelephonyConferencing extends OperationSet { /** - * Creates a conference call with the specified callees as call - * peers. + * Creates a conference call with the specified callees as call peers. * - * @param callees - * the list of addresses that we should call + * @param callees the list of addresses that we should call * @return the newly created conference call containing all CallPeers - * @throws OperationNotSupportedException - * if the provider does not have any conferencing features. + * @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 OperationNotSupportedException; + throws OperationFailedException, + OperationNotSupportedException; /** * Invites the callee represented by the specified uri to an already * existing call. The difference between this method and createConfCall is * that inviteCalleeToCall allows a user to transform an existing 1 to 1 - * call into a conference call, or add new peers to an already - * established conference. + * call into a conference call, or add new peers to an already established + * conference. * - * @param uri - * the callee to invite to an existing conf call. - * @param existingCall - * the call that we should invite the callee to. - * @return the CallPeer object corresponding to the callee - * represented by the specified uri. - * @throws OperationNotSupportedException - * if allowing additional callees to a pre-established call is - * not supported. + * @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 existingCall) - throws OperationNotSupportedException; + public CallPeer inviteCalleeToCall(String uri, Call call) + throws OperationFailedException, + OperationNotSupportedException; }