diff --git a/src/net/java/sip/communicator/impl/callhistory/CallHistoryServiceImpl.java b/src/net/java/sip/communicator/impl/callhistory/CallHistoryServiceImpl.java index aed3ee75d..648f5f980 100644 --- a/src/net/java/sip/communicator/impl/callhistory/CallHistoryServiceImpl.java +++ b/src/net/java/sip/communicator/impl/callhistory/CallHistoryServiceImpl.java @@ -1077,7 +1077,7 @@ public int compare(Object o1, Object o2) * Receive events for adding or removing participants from a call */ private class HistoryCallChangeListener - implements CallChangeListener + extends CallChangeAdapter { public void callParticipantAdded(CallParticipantEvent evt) { @@ -1089,7 +1089,5 @@ public void callParticipantRemoved(CallParticipantEvent evt) handleParticipantRemoved(evt.getSourceCallParticipant(), evt.getSourceCall()); } - - public void callStateChanged(CallChangeEvent evt){} } } diff --git a/src/net/java/sip/communicator/impl/media/CallSessionImpl.java b/src/net/java/sip/communicator/impl/media/CallSessionImpl.java index de7b45ece..bbebc7a84 100644 --- a/src/net/java/sip/communicator/impl/media/CallSessionImpl.java +++ b/src/net/java/sip/communicator/impl/media/CallSessionImpl.java @@ -382,11 +382,19 @@ private void startStreaming() * Stops and closes all streams that have been initialized for local * RTP managers. */ - private void stopStreaming() + public void stopStreaming() { - stopStreaming(getAudioRtpManager(), "audio"); + RTPManager audioRtpManager = getAudioRtpManager(); + if (audioRtpManager != null) + { + stopStreaming(audioRtpManager, "audio"); + } this.audioRtpManager = null; - stopStreaming(getVideoRtpManager(), "video"); + RTPManager videoRtpManager = getAudioRtpManager(); + if (videoRtpManager != null) + { + stopStreaming(videoRtpManager, "video"); + } this.videoRtpManager = null; } diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/ActiveCallsRepository.java b/src/net/java/sip/communicator/impl/protocol/jabber/ActiveCallsRepository.java index 354a2c785..c09c95700 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/ActiveCallsRepository.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/ActiveCallsRepository.java @@ -6,10 +6,11 @@ */ package net.java.sip.communicator.impl.protocol.jabber; -import net.java.sip.communicator.util.*; import java.util.*; -import net.java.sip.communicator.service.protocol.event.*; + import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.util.*; import org.jivesoftware.smackx.jingle.*; /** @@ -21,7 +22,7 @@ * @author Symphorien Wanko */ public class ActiveCallsRepository - implements CallChangeListener + extends CallChangeAdapter { /** * logger of this class @@ -60,22 +61,6 @@ public void addCall(CallJabberImpl call) call.addCallChangeListener(this); } - /** - * A dummy implementation of the CallChangeListener method that we don't - * use. - * @param evt unused. - */ - public void callParticipantAdded(CallParticipantEvent evt) - {} - - /** - * A dummy implementation of the CallChangeListener method that we don't - * use. - * @param evt unused. - */ - public void callParticipantRemoved(CallParticipantEvent evt) - {} - /** * If evt indicates that the call has been ended we remove it from * the repository. diff --git a/src/net/java/sip/communicator/impl/protocol/sip/ActiveCallsRepository.java b/src/net/java/sip/communicator/impl/protocol/sip/ActiveCallsRepository.java index 5e353dfdf..f96eef3a2 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/ActiveCallsRepository.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/ActiveCallsRepository.java @@ -6,12 +6,13 @@ */ package net.java.sip.communicator.impl.protocol.sip; -import net.java.sip.communicator.util.*; import java.util.*; -import net.java.sip.communicator.service.protocol.event.*; -import net.java.sip.communicator.service.protocol.*; import javax.sip.*; +import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.protocol.event.*; +import net.java.sip.communicator.util.*; + /** * Keeps a list of all calls currently active and maintained by this protocol * povider. Offers methods for finding a call by its ID, participant dialog @@ -20,7 +21,7 @@ * @author Emil Ivov */ public class ActiveCallsRepository - implements CallChangeListener + extends CallChangeAdapter { private static final Logger logger = Logger.getLogger(ActiveCallsRepository.class); @@ -51,22 +52,6 @@ public void addCall(CallSipImpl call) call.addCallChangeListener(this); } - /** - * A dummy implementation of the CallChangeListener method that we don't - * use. - * @param evt unused. - */ - public void callParticipantAdded(CallParticipantEvent evt) - {} - - /** - * A dummy implementation of the CallChangeListener method that we don't - * use. - * @param evt unused. - */ - public void callParticipantRemoved(CallParticipantEvent evt) - {} - /** * If evt indicates that the call has been ended we remove it from * the repository. diff --git a/src/net/java/sip/communicator/impl/protocol/sip/DialogUtils.java b/src/net/java/sip/communicator/impl/protocol/sip/DialogUtils.java new file mode 100644 index 000000000..94aafc18d --- /dev/null +++ b/src/net/java/sip/communicator/impl/protocol/sip/DialogUtils.java @@ -0,0 +1,255 @@ +/* + * 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 java.util.*; +import javax.sip.*; + +/** + * Implements utility methods to aid the manipulation of {@link Dialog} + * instances and extend the mentioned type with additional functionality. + * + * @author Lubomir Marinov + */ +public final class DialogUtils +{ + + /** + * Associates a specific subscription with the a given Dialog + * in order to allow it to keep the dialog in question alive even after a + * BYE request. + * + * @param dialog the Dialog to associate the subscription with + * and to be kept alive after a BYE request because of the + * subscription + * @param subscription the subscription to be associated with + * dialog and keep it alive after a BYE request + * @return true if the specified subscription was associated with + * the given dialog; false if no changes were applied + * @throws SipException + */ + public static boolean addSubscription(Dialog dialog, Object subscription) + throws SipException + { + synchronized (dialog) + { + DialogApplicationData applicationData = + (DialogApplicationData) dialog.getApplicationData(); + if (applicationData == null) + { + applicationData = new DialogApplicationData(); + dialog.setApplicationData(applicationData); + } + + if (applicationData.addSubscription(subscription)) + { + try + { + dialog.terminateOnBye(false); + return true; + } + catch (SipException ex) + { + + /* + * Since the subscription didn't quite register, undo the + * part of the registration which did succeed. + */ + applicationData.removeSubscription(subscription); + throw ex; + } + } + return false; + } + } + + /** + * Processes a BYE request in a specific Dialog for the + * purposes of subscription associations and returns an indicator which + * determines whether the specified dialog should still be considered alive + * after the processing of the BYE request. + * + * @param dialog the Dialog in which a BYE request has arrived + * @return true if dialog should still be considered + * alive after processing the mentioned BYE request; false + * if dialog is to be expected to die after processing + * the request in question + * @throws SipException + */ + public static boolean processByeThenIsDialogAlive(Dialog dialog) + throws SipException + { + synchronized (dialog) + { + DialogApplicationData applicationData = + (DialogApplicationData) dialog.getApplicationData(); + if (applicationData != null) + { + applicationData.setByeProcessed(true); + + if (applicationData.getSubscriptionCount() > 0) + { + dialog.terminateOnBye(false); + return true; + } + } + return false; + } + } + + /** + * Dissociates a specific subscription with a given Dialog in + * order to no longer allow it to keep the dialog in question alive even + * after a BYE request, deletes the dialog if there are no other + * subscriptions associated with it and a BYE request has already been + * received and returns an indicator which determines whether the specified + * dialog is still alive after the dissociation of the given subscription. + * + * @param dialog the Dialog to dissociate the subscription with + * and to no longer be kept alive after a BYE request because of + * the subscription + * @param subscription the subscription to be dissociated with + * dialog and to no longer be kept alive after a BYE + * request because of the subscription + * @return true if the dialog is still alive after the + * dissociation; false if the dialog was terminated because + * of the dissociation + */ + public static boolean removeSubscriptionThenIsDialogAlive(Dialog dialog, + Object subscription) + { + synchronized (dialog) + { + DialogApplicationData applicationData = + (DialogApplicationData) dialog.getApplicationData(); + if ((applicationData != null) + && applicationData.removeSubscription(subscription) + && (applicationData.getSubscriptionCount() <= 0) + && applicationData.isByeProcessed()) + { + dialog.delete(); + return false; + } + return true; + } + } + + /** + * Prevents the creation of DialogUtils instances. + */ + private DialogUtils() + { + } + + /** + * Represents the application-specific data which the SIP protocol provider + * associates with {@link #Dialog} instances. + *

+ * The implementation at the time of this writing allows tracking + * subscriptions in a specific Dialog in order to make it + * possible to determine whether a BYE request should terminate the + * respective dialog. + *

+ */ + private static class DialogApplicationData + { + + /** + * The indicator which determines whether a BYE request has already been + * processed in the owning Dialog and thus allows + * determining whether the dialog in question should be terminated when + * the last associated subscription is terminated. + */ + private boolean byeIsProcessed; + + /** + * The set of subscriptions not yet terminated in the owning + * Dialog i.e. keeping it alive even after a BYE request. + */ + private final List subscriptions = new ArrayList(); + + /** + * Associates a specific subscription with the owning + * Dialog in order to allow it to keep the dialog in + * question alive even after a BYE request. + * + * @param subscription the subscription with no specific type of + * interest to this implementation to be associated with the + * owning Dialog + * @return true if the specified subscription caused a + * modification of the list of associated subscriptions; + * false if no change to the mentioned list was applied + */ + public boolean addSubscription(Object subscription) + { + if (!subscriptions.contains(subscription)) + { + return subscriptions.add(subscription); + } + return false; + } + + /** + * Determines whether a BYE request has already been processed in the + * owning Dialog and thus allows determining whether the + * dialog in question should be terminated when the last associated + * subscription is terminated. + * + * @return true if a BYE request has already been processed in + * the owning Dialog; false, otherwise + */ + public boolean isByeProcessed() + { + return byeIsProcessed; + } + + /** + * Determines the number of subscriptions associated with the owning + * Dialog i.e. keeping it alive even after a BYE request. + * + * @return the number of subscriptions associated with the owning + * Dialog i.e. keeping it alive even after a BYE + * request + */ + public int getSubscriptionCount() + { + return subscriptions.size(); + } + + /** + * Dissociates a specific subscription with the owning + * Dialog in order to no longer allow it to keep the dialog + * in question alive even after a BYE request. + * + * @param subscription the subscription with no specific type of + * interest to this implementation to be dissociated with the + * owning Dialog + * @return true if the specified subscription caused a + * modification of the list of associated subscriptions; + * false if no change to the mentioned list was applied + */ + public boolean removeSubscription(Object subscription) + { + return subscriptions.remove(subscription); + } + + /** + * Sets the indicator which determines whether a BYE request has already + * been processed in the owning Dialog and thus allows + * determining whether the dialog in question should be terminated when + * the last associated subscription is terminated. + * + * @param byeIsProcessed true if a BYE request has already been + * processed in the owning Dialog; + * false, otherwise + */ + public void setByeProcessed(boolean byeIsProcessed) + { + this.byeIsProcessed = byeIsProcessed; + } + } +} 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 f4b85a494..b58d8a987 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/OperationSetBasicTelephonySipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/OperationSetBasicTelephonySipImpl.java @@ -72,6 +72,7 @@ public OperationSetBasicTelephonySipImpl( protocolProvider.registerMethodProcessor(Request.CANCEL, this); protocolProvider.registerMethodProcessor(Request.ACK, this); protocolProvider.registerMethodProcessor(Request.BYE, this); + protocolProvider.registerMethodProcessor(Request.REFER, this); } /** @@ -89,7 +90,7 @@ public OperationSetBasicTelephonySipImpl( * @throws ParseException if callee is not a valid sip address * string. */ - public synchronized Call createCall(String callee) + public Call createCall(String callee) throws OperationFailedException, ParseException { Address toAddress = parseAddressStr(callee); @@ -110,7 +111,7 @@ public synchronized Call createCall(String callee) * @throws OperationFailedException with the corresponding code if we fail * to create the call. */ - public synchronized Call createCall(Contact callee) + public Call createCall(Contact callee) throws OperationFailedException { Address toAddress = null; @@ -145,7 +146,7 @@ public synchronized Call createCall(Contact callee) * @throws OperationFailedException with the corresponding code if we fail * to create the call. */ - private CallSipImpl createOutgoingCall(Address calleeAddress) + private synchronized CallSipImpl createOutgoingCall(Address calleeAddress) throws OperationFailedException { //create the invite request @@ -394,30 +395,7 @@ private void sendInviteRequest(CallParticipantSipImpl sipParticipant, String sdpOffer) throws OperationFailedException { Dialog dialog = sipParticipant.getDialog(); - Request invite = null; - try - { - invite = dialog.createRequest(Request.INVITE); - } - catch (SipException ex) - { - throwOperationFailedException("Failed to create invite request.", - OperationFailedException.INTERNAL_ERROR, ex); - } - - /* - * The authorization-related headers are the responsibility of the - * application (according to the Javadoc of JAIN-SIP). - */ - AuthorizationHeader authorization = - protocolProvider.getSipSecurityManager() - .getCachedAuthorizationHeader( - ((CallIdHeader) invite.getHeader(CallIdHeader.NAME)) - .getCallId()); - if (authorization != null) - { - invite.setHeader(authorization); - } + Request invite = createRequest(dialog, Request.INVITE); try { @@ -492,6 +470,7 @@ public void processRequest(RequestEvent requestEvent) .getServerTransaction(); SipProvider jainSipProvider = (SipProvider)requestEvent.getSource(); Request request = requestEvent.getRequest(); + String requestMethod = request.getMethod(); if (serverTransaction == null) { @@ -516,12 +495,12 @@ public void processRequest(RequestEvent requestEvent) + "transaction for an incoming request\n" + "(Next message contains the request)" , ex); - return; + return; } } //INVITE - if (request.getMethod().equals(Request.INVITE)) + if (requestMethod.equals(Request.INVITE)) { logger.debug("received INVITE"); DialogState dialogState = serverTransaction.getDialog().getState(); @@ -540,21 +519,27 @@ public void processRequest(RequestEvent requestEvent) } } //ACK - else if (request.getMethod().equals(Request.ACK)) + else if (requestMethod.equals(Request.ACK)) { processAck(serverTransaction, request); } //BYE - else if (request.getMethod().equals(Request.BYE)) + else if (requestMethod.equals(Request.BYE)) { processBye(serverTransaction, request); } //CANCEL - else if (request.getMethod().equals(Request.CANCEL)) + else if (requestMethod.equals(Request.CANCEL)) { processCancel(serverTransaction, request); } + // REFER + else if (requestMethod.equals(Request.REFER)) + { + logger.debug("received REFER"); + processRefer(serverTransaction, request, jainSipProvider); } + } /** * Process an asynchronously reported TransactionTerminatedEvent. @@ -1648,8 +1633,9 @@ private void processBye(ServerTransaction serverTransaction, Request byeRequest) { //find the call - CallParticipantSipImpl callParticipant = activeCallsRepository - .findCallParticipant( serverTransaction.getDialog()); + Dialog dialog = serverTransaction.getDialog(); + CallParticipantSipImpl callParticipant = + activeCallsRepository.findCallParticipant(dialog); if (callParticipant == null) { logger.debug("Received a stray bye request."); @@ -1659,10 +1645,7 @@ private void processBye(ServerTransaction serverTransaction, //Send OK Response ok = null; try { - ok = protocolProvider.getMessageFactory() - .createResponse(Response.OK, byeRequest); - protocolProvider.attachToTag(ok, serverTransaction.getDialog()); - ok.setHeader(protocolProvider.getSipCommUserAgentHeader()); + ok = createOKResponse(byeRequest, dialog); } catch (ParseException ex) { logger.error("Error while trying to send a response to a bye", ex); @@ -1683,9 +1666,30 @@ private void processBye(ServerTransaction serverTransaction, ex); } - //change status - callParticipant.setState(CallParticipantState.DISCONNECTED); + // change status + boolean dialogIsAlive; + try + { + dialogIsAlive = DialogUtils.processByeThenIsDialogAlive(dialog); + } + catch (SipException ex) + { + dialogIsAlive = false; + logger + .error( + "Failed to determine whether the dialog should stay alive.", + ex); + } + if (dialogIsAlive) + { + ((CallSipImpl) callParticipant.getCall()).getMediaCallSession() + .stopStreaming(); + } + else + { + callParticipant.setState(CallParticipantState.DISCONNECTED); + } } /** @@ -1740,12 +1744,10 @@ void processCancel(ServerTransaction serverTransaction, // Cancels should be OK-ed and the initial transaction - terminated // (report and fix by Ranga) - try { - Response ok = protocolProvider.getMessageFactory().createResponse( - Response.OK, cancelRequest); - - protocolProvider.attachToTag(ok, serverTransaction.getDialog()); - ok.setHeader(protocolProvider.getSipCommUserAgentHeader()); + try + { + Response ok = + createOKResponse(cancelRequest, serverTransaction.getDialog()); serverTransaction.sendResponse(ok); logger.debug("sent an ok response to a CANCEL request:\n" + ok); @@ -1803,9 +1805,398 @@ void processCancel(ServerTransaction serverTransaction, callParticipant.setState(CallParticipantState.DISCONNECTED); } + /** + * Processes a specific REFER request i.e. attempts to transfer the + * call/call participant receiving the request to a specific transfer + * target. + * + * @param serverTransaction the ServerTransaction containing + * the REFER request + * @param referRequest the very REFER request + * @param sipProvider the provider containing serverTransaction + */ + private void processRefer(ServerTransaction serverTransaction, + final Request referRequest, final SipProvider sipProvider) + { + ReferToHeader referToHeader = + (ReferToHeader) referRequest.getHeader(ReferToHeader.NAME); + if (referToHeader == null) + { + logger.error("No Refer-To header in REFER request:\n" + + referRequest); + return; + } + Address referToAddress = referToHeader.getAddress(); + if (referToAddress == null) + { + logger.error("No address in REFER request Refer-To header:\n" + + referRequest); + return; + } + + // OK + final Dialog dialog = serverTransaction.getDialog(); + Response ok = null; + boolean removeSubscription = false; + try + { + ok = createOKResponse(referRequest, dialog); + } + catch (ParseException ex) + { + logger.error("Failed to create OK response to REFER request:\n" + + referRequest, ex); + /* + * TODO Should the call transfer not be attempted because the OK + * couldn't be sent? + */ + } + if (ok != null) + { + Throwable failure = null; + try + { + serverTransaction.sendResponse(ok); + } + catch (InvalidArgumentException ex) + { + failure = ex; + } + catch (SipException ex) + { + failure = ex; + } + if (failure != null) + { + ok = null; + + logger.error("Failed to send OK response to REFER request:\n" + + referRequest, failure); + /* + * TODO Should the call transfer not be attempted because the OK + * couldn't be sent? + */ + } + else + { + + /* + * The REFER request has created a subscription. Take it into + * consideration in order to not disconnect on BYE but rather + * when the last subscription terminates. + */ + try + { + removeSubscription = + DialogUtils.addSubscription(dialog, referRequest); + } + catch (SipException ex) + { + logger.error( + "Failed to make the REFER request keep the dialog alive after BYE:\n" + + referRequest, ex); + } + + // NOTIFY Trying + try + { + sendReferNotifyRequest(dialog, + SubscriptionStateHeader.ACTIVE, null, + "SIP/2.0 100 Trying", sipProvider); + } + catch (OperationFailedException ex) + { + /* + * TODO Determine whether the failure to send the Trying + * refer NOTIFY should prevent the sending of the + * session-terminating refer NOTIFY. + */ + } + } + } + + /* + * Regardless of whether the OK, NOTIFY, etc. succeeded, try to + * transfer the call because it's the most important goal. + */ + Call referToCall; + try + { + referToCall = createOutgoingCall(referToAddress); + } + catch (OperationFailedException ex) + { + referToCall = null; + + logger.error("Failed to create outgoing call to " + + referToAddress, ex); + } + + /* + * Start monitoring the call in order to discover when the + * subscription-terminating NOTIFY with the final result of the REFER is + * to be sent. + */ + final Call referToCallListenerSource = referToCall; + final boolean sendNotifyRequest = (ok != null); + final Object subscription = (removeSubscription ? referRequest : null); + CallChangeListener referToCallListener = new CallChangeAdapter() + { + + /** + * The indicator which determines whether the job of this listener + * has been done i.e. whether a single subscription-terminating + * NOTIFY with the final result of the REFER has been sent. + */ + private boolean done; + + public synchronized void callStateChanged(CallChangeEvent evt) + { + if (!done + && referToCallStateChanged(referToCallListenerSource, + sendNotifyRequest, dialog, sipProvider, subscription)) + { + done = true; + if (referToCallListenerSource != null) + { + referToCallListenerSource + .removeCallChangeListener(this); + } + } + } + }; + if (referToCall != null) + { + referToCall.addCallChangeListener(referToCallListener); + } + referToCallListener.callStateChanged(null); + } + + /** + * Tracks the state changes of a specific Call and sends a + * session-terminating NOTIFY request to the Dialog which + * referred to the call in question as soon as the outcome of the refer is + * determined. + * + * @param referToCall the Call to track and send a NOTIFY + * request for + * @param sendNotifyRequest true if a session-terminating NOTIFY + * request should be sent to the Dialog which + * referred to referToCall; false to send + * no such NOTIFY request + * @param dialog the Dialog which initiated the specified call + * as part of processing a REFER request + * @param sipProvider the SipProvider to send the NOTIFY + * request through + * @param subscription the subscription to be terminated when the NOTIFY + * request is sent + * @return true if a session-terminating NOTIFY request was sent + * and the state of referToCall should no longer be + * tracked; false if it's too early to send a + * session-terminating NOTIFY request and the tracking of the state + * of referToCall should continue + */ + private boolean referToCallStateChanged(Call referToCall, + boolean sendNotifyRequest, Dialog dialog, SipProvider sipProvider, + Object subscription) + { + CallState referToCallState = + (referToCall == null) ? null : referToCall.getCallState(); + if (CallState.CALL_INITIALIZATION.equals(referToCallState)) + { + return false; + } + + /* + * NOTIFY OK/Declined + * + * It doesn't sound like sending NOTIFY Service Unavailable is + * appropriate because the REFER request has (presumably) already been + * accepted. + */ + if (sendNotifyRequest) + { + String referStatus = + CallState.CALL_IN_PROGRESS.equals(referToCallState) ? "SIP/2.0 200 OK" + : "SIP/2.0 603 Declined"; + try + { + sendReferNotifyRequest(dialog, + SubscriptionStateHeader.TERMINATED, + SubscriptionStateHeader.NO_RESOURCE, referStatus, + sipProvider); + } + catch (OperationFailedException ex) + { + // The exception has already been logged. + } + } + + /* + * Whatever the status of the REFER is, the subscription created by it + * is terminated with the final NOTIFY. + */ + if (DialogUtils.removeSubscriptionThenIsDialogAlive(dialog, + subscription) == false) + { + CallParticipantSipImpl callParticipant = + activeCallsRepository.findCallParticipant(dialog); + if (callParticipant != null) + { + callParticipant.setState(CallParticipantState.DISCONNECTED); + } + } + return true; + } + + /** + * Sends a Request.NOTIFY request in a specific + * Dialog as part of the communication associated with an + * earlier-received Request.REFER request. The sent NOTIFY has + * a specific Subscription-State header and reason, carries a + * specific body content and is sent through a specific + * SipProvider. + * + * @param dialog the Dialog to send the NOTIFY request in + * @param subscriptionState the Subscription-State header to be + * sent with the NOTIFY request + * @param reasonCode the reason for the specified + * subscriptionState if any; null otherwise + * @param content the content to be carried in the body of the sent NOTIFY + * request + * @param sipProvider the SipProvider to send the NOTIFY + * request through + * @throws OperationFailedException + */ + private void sendReferNotifyRequest(Dialog dialog, + String subscriptionState, String reasonCode, Object content, + SipProvider sipProvider) throws OperationFailedException + { + Request notify = createRequest(dialog, Request.NOTIFY); + HeaderFactory headerFactory = protocolProvider.getHeaderFactory(); + + // Populate the request. + String eventType = "refer"; + try + { + notify.setHeader(headerFactory.createEventHeader(eventType)); + } + catch (ParseException ex) + { + throwOperationFailedException("Failed to create " + eventType + + " Event header.", OperationFailedException.INTERNAL_ERROR, ex); + } + + SubscriptionStateHeader ssHeader = null; + try + { + ssHeader = + headerFactory.createSubscriptionStateHeader(subscriptionState); + if (reasonCode != null) + ssHeader.setReasonCode(reasonCode); + } + catch (ParseException ex) + { + throwOperationFailedException("Failed to create " + + subscriptionState + " Subscription-State header.", + OperationFailedException.INTERNAL_ERROR, ex); + } + notify.setHeader(ssHeader); + + ContentTypeHeader ctHeader = null; + try + { + ctHeader = + headerFactory.createContentTypeHeader("message", "sipfrag"); + } + catch (ParseException ex) + { + throwOperationFailedException( + "Failed to create Content-Type header.", + OperationFailedException.INTERNAL_ERROR, ex); + } + try + { + notify.setContent(content, ctHeader); + } + catch (ParseException ex) + { + throwOperationFailedException("Failed to set NOTIFY body/content.", + OperationFailedException.INTERNAL_ERROR, ex); + } + + // Send the request. + ClientTransaction clientTransaction = null; + try + { + clientTransaction = sipProvider.getNewClientTransaction(notify); + } + catch (TransactionUnavailableException ex) + { + throwOperationFailedException( + "Failed to create a client transaction for the new refer NOTIFY.", + OperationFailedException.INTERNAL_ERROR, ex); + } + try + { + dialog.sendRequest(clientTransaction); + } + catch (SipException ex) + { + throwOperationFailedException( + "Failed to send the new refer NOTIFY.", + OperationFailedException.NETWORK_FAILURE, ex); + } + } + + /** + * Creates a new {@link Request} of a specific method which is to be sent in + * a specific Dialog and populates its generally-necessary + * headers such as the Authorization header. + * + * @param dialog the Dialog to create the new + * Request in + * @param method the method of the newly-created Request + * @return a new {@link Request} of the specified method which + * is to be sent in the specified dialog and populated + * with its generally-necessary headers such as the Authorization + * header + * @throws OperationFailedException + */ + private Request createRequest(Dialog dialog, String method) + throws OperationFailedException + { + Request request = null; + try + { + request = dialog.createRequest(method); + } + catch (SipException ex) + { + throwOperationFailedException("Failed to create " + method + + " request.", OperationFailedException.INTERNAL_ERROR, ex); + } + + /* + * The authorization-related headers are the responsibility of the + * application (according to the Javadoc of JAIN-SIP). + */ + AuthorizationHeader authorization = + protocolProvider.getSipSecurityManager() + .getCachedAuthorizationHeader( + ((CallIdHeader) request.getHeader(CallIdHeader.NAME)) + .getCallId()); + if (authorization != null) + { + request.setHeader(authorization); + } + + return request; + } + /** * Indicates a user request to end a call with the specified call - * particiapnt. Depending on the state of the call the method would send a + * participant. Depending on the state of the call the method would send a * CANCEL, BYE, or BUSY_HERE and set the new state to DISCONNECTED. * * @param participant the participant that we'd like to hang up on. @@ -2177,12 +2568,11 @@ public synchronized void answerCallParticipant(CallParticipant participant) ServerTransaction serverTransaction = (ServerTransaction) transaction; Response ok = null; - try { - ok = protocolProvider.getMessageFactory().createResponse( - Response.OK - ,callParticipant.getFirstTransaction().getRequest()); - ok.setHeader(protocolProvider.getSipCommUserAgentHeader()); - protocolProvider.attachToTag(ok, dialog); + try + { + ok = + createOKResponse(callParticipant.getFirstTransaction() + .getRequest(), dialog); } catch (ParseException ex) { callParticipant.setState(CallParticipantState.DISCONNECTED); @@ -2292,6 +2682,28 @@ public synchronized void answerCallParticipant(CallParticipant participant) } } //answer call + /** + * Creates a new {@link Response#OK} response to a specific {@link Request} + * which is to be sent as part of a specific {@link Dialog}. + * + * @param request the Request to create the OK response for + * @param containingDialog the Dialog to send the response in + * @return a new + * Response.OK response to the specified request + * to be sent as part of the specified containingDialog + * @throws ParseException + */ + private Response createOKResponse(Request request, Dialog containingDialog) + throws ParseException + { + Response ok = + protocolProvider.getMessageFactory().createResponse(Response.OK, + request); + protocolProvider.attachToTag(ok, containingDialog); + ok.setHeader(protocolProvider.getSipCommUserAgentHeader()); + return ok; + } + /** * Creates a new call and call participant associated with * containingTransaction diff --git a/src/net/java/sip/communicator/service/media/CallSession.java b/src/net/java/sip/communicator/service/media/CallSession.java index b38852156..e6ab6f2c0 100644 --- a/src/net/java/sip/communicator/service/media/CallSession.java +++ b/src/net/java/sip/communicator/service/media/CallSession.java @@ -184,4 +184,10 @@ public void processSdpAnswer(CallParticipant responder, String sdpAnswer) * false */ public void setMute(boolean mute); + + /** + * Stops and closes the audio and video streams flowing through this + * session. + */ + public void stopStreaming(); } diff --git a/src/net/java/sip/communicator/service/protocol/event/CallChangeAdapter.java b/src/net/java/sip/communicator/service/protocol/event/CallChangeAdapter.java new file mode 100644 index 000000000..e45f90b10 --- /dev/null +++ b/src/net/java/sip/communicator/service/protocol/event/CallChangeAdapter.java @@ -0,0 +1,61 @@ +/* + * 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.service.protocol.event; + +/** + * An abstract adapter class for receiving call-state (change) events. This + * class exists only as a convenience for creating listener objects. + *

+ * Extend this class to create a CallChangeEvent listener and + * override the methods for the events of interest. (If you implement the + * CallChangeListener interface, you have to define all of the + * methods in it. This abstract class defines null methods for them all, so you + * only have to define methods for events you care about.) + *

+ * + * @see CallChangeEvent + * @see CallChangeListener + * + * @author Lubomir Marinov + */ +public abstract class CallChangeAdapter + implements CallChangeListener +{ + + /* + * (non-Javadoc) + * + * @seenet.java.sip.communicator.service.protocol.event.CallChangeListener# + * callParticipantAdded + * (net.java.sip.communicator.service.protocol.event.CallParticipantEvent) + */ + public void callParticipantAdded(CallParticipantEvent evt) + { + } + + /* + * (non-Javadoc) + * + * @seenet.java.sip.communicator.service.protocol.event.CallChangeListener# + * callParticipantRemoved + * (net.java.sip.communicator.service.protocol.event.CallParticipantEvent) + */ + public void callParticipantRemoved(CallParticipantEvent evt) + { + } + + /* + * (non-Javadoc) + * + * @seenet.java.sip.communicator.service.protocol.event.CallChangeListener# + * callStateChanged + * (net.java.sip.communicator.service.protocol.event.CallChangeEvent) + */ + public void callStateChanged(CallChangeEvent evt) + { + } +} diff --git a/test/net/java/sip/communicator/slick/protocol/jabber/TestOperationSetBasicTelephonyJabberImpl.java b/test/net/java/sip/communicator/slick/protocol/jabber/TestOperationSetBasicTelephonyJabberImpl.java index 6e1c6e4eb..d04a7dcbb 100644 --- a/test/net/java/sip/communicator/slick/protocol/jabber/TestOperationSetBasicTelephonyJabberImpl.java +++ b/test/net/java/sip/communicator/slick/protocol/jabber/TestOperationSetBasicTelephonyJabberImpl.java @@ -983,7 +983,7 @@ public void waitForEvent(long waitFor, boolean exitIfAlreadyInState) * changes. */ public class CallStateEventCollector - implements CallChangeListener + extends CallChangeAdapter { public ArrayList collectedEvents = new ArrayList(); private Call listenedCall = null; @@ -1024,22 +1024,6 @@ public void callStateChanged(CallChangeEvent event) } } - /** - * Unused by this collector. - * @param event ignored. - */ - public void callParticipantAdded(CallParticipantEvent event) - { - } - - /** - * Unused by this collector. - * @param event ignored. - */ - public void callParticipantRemoved(CallParticipantEvent event) - { - } - /** * Blocks until an event notifying us of the awaited state change is * received or until waitFor miliseconds pass (whichever happens first). diff --git a/test/net/java/sip/communicator/slick/protocol/sip/TestOperationSetBasicTelephonySipImpl.java b/test/net/java/sip/communicator/slick/protocol/sip/TestOperationSetBasicTelephonySipImpl.java index 21d3508e6..ad704a469 100644 --- a/test/net/java/sip/communicator/slick/protocol/sip/TestOperationSetBasicTelephonySipImpl.java +++ b/test/net/java/sip/communicator/slick/protocol/sip/TestOperationSetBasicTelephonySipImpl.java @@ -982,7 +982,7 @@ public void waitForEvent(long waitFor, boolean exitIfAlreadyInState) * changes. */ public class CallStateEventCollector - implements CallChangeListener + extends CallChangeAdapter { public ArrayList collectedEvents = new ArrayList(); private Call listenedCall = null; @@ -1023,22 +1023,6 @@ public void callStateChanged(CallChangeEvent event) } } - /** - * Unused by this collector. - * @param event ignored. - */ - public void callParticipantAdded(CallParticipantEvent event) - { - } - - /** - * Unused by this collector. - * @param event ignored. - */ - public void callParticipantRemoved(CallParticipantEvent event) - { - } - /** * Blocks until an event notifying us of the awaited state change is * received or until waitFor miliseconds pass (whichever happens first).