/* * 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.text.*; import javax.sip.*; import javax.sip.message.*; import gov.nist.javax.sip.header.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.util.*; /** * Class responsible for sending a DTMF Tone using SIP INFO. * * @todo - remove the following comment once this class has been sufficiently * tested. * * It should actually only be considered as a draft, since it was not heavily * tested, and just developed enough to get the DTMF function work I copied * and adapted code of the OpSetBasicTelephony implementation for SIP to make * this code. * * @author JM HEITZ */ public class OperationSetDTMFSipImpl implements MethodProcessor, OperationSetDTMF { /** * logger for the class */ private static final Logger logger = Logger.getLogger(OperationSetDTMFSipImpl.class); /** * involved protocol provider service */ ProtocolProviderServiceSipImpl pps = null; /** *Constructor * * @param pps the SIP Protocol provider service */ public OperationSetDTMFSipImpl(ProtocolProviderServiceSipImpl pps) { this.pps = pps; pps.registerMethodProcessor(Request.INFO, this); } /** * This is just a copy of the bye method from the OpSetBasicTelephony, * which was enhanced with a body in order to send the DTMF tone * * @param callParticipant destination of the DTMF tone * @param dtmftone DTMF tone to send * @throws OperationFailedException */ private void sayInfo(CallParticipantSipImpl callParticipant, String dtmftone) throws OperationFailedException { Request info = null; try { info = callParticipant.getDialog().createRequest(Request.INFO); } catch (SipException ex) { logger.error("Failed to create info request!", ex); throw new OperationFailedException( "Failed to create info request!" , OperationFailedException.INTERNAL_ERROR , ex); } //here we add the body ContentType ct = new ContentType("application", "dtmf-relay"); String content = "Signal=" + dtmftone + "\r\nDuration=250\r\n"; ContentLength cl = new ContentLength(content.length()); info.setContentLength(cl); try { info.setContent(content.getBytes(), ct); } catch (ParseException ex) { logger.error("Failed to construct the INFO request", ex); throw new OperationFailedException( "Failed to construct a client the INFO request" , OperationFailedException.INTERNAL_ERROR , ex); } //body ended ClientTransaction clientTransaction = null; try { clientTransaction = callParticipant.getJainSipProvider() .getNewClientTransaction(info); } catch (TransactionUnavailableException ex) { logger.error( "Failed to construct a client transaction from the INFO request" , ex); throw new OperationFailedException( "Failed to construct a client transaction from the INFO request" , OperationFailedException.INTERNAL_ERROR , ex); } try { if (callParticipant.getDialog().getState() == DialogState.TERMINATED) { //this is probably because the call has just ended, so don't //throw an exception. simply log and get lost. logger.warn("Trying to send a dtmf tone inside a " +"TERMINATED dialog."); return; } callParticipant.getDialog().sendRequest(clientTransaction); logger.debug("sent request:\n" + info); } catch (SipException ex) { throw new OperationFailedException( "Failed to send the INFO request" , OperationFailedException.NETWORK_FAILURE , ex); } } /** * Does nothing * * @param requestEvent the event request * @return <tt>true</tt> if the specified event has been handled by this * processor and shouldn't be offered to other processors registered * for the same method; <tt>false</tt>, otherwise */ public boolean processRequest(RequestEvent requestEvent) { if (requestEvent == null) { logger.debug("requestEvent null"); } logger.error("We don't cope with requests" + requestEvent); return false; } /** * Just look if the DTMF signal was well received, and log it * * @param responseEvent the response event * @return <tt>true</tt> if the specified event has been handled by this * processor and shouldn't be offered to other processors registered * for the same method; <tt>false</tt>, otherwise */ public boolean processResponse(ResponseEvent responseEvent) { if (responseEvent == null) { logger.debug("null responseEvent"); return false; } Response response = responseEvent.getResponse(); if (response == null) { logger.debug("null response"); return false; } int code = response.getStatusCode(); logger.debug("DTMF status code=" + code); if (code != 200) { logger.error("DTMF Send failed :" + code); } else { logger.debug("DTMF succeeded"); } return true; } /** * In case of timeout, just terminate the transaction * * @param timeoutEvent the timeout event * @return <tt>true</tt> if the specified event has been handled by this * processor and shouldn't be offered to other processors registered * for the same method; <tt>false</tt>, otherwise */ public boolean processTimeout(TimeoutEvent timeoutEvent) { //we do nothing logger.error("ioexception :" + timeoutEvent); return false; } /** * Just log the exception * * @param exceptionEvent the event we have to handle * @return <tt>true</tt> if the specified event has been handled by this * processor and shouldn't be offered to other processors registered * for the same method; <tt>false</tt>, otherwise */ public boolean processIOException(IOExceptionEvent exceptionEvent) { //we do nothing if (exceptionEvent == null) { logger.debug("ioexception null"); return false; } logger.error("ioexception :" + exceptionEvent); return false; } /** * Just log the end of the transaction * * @param transactionTerminatedEvent the event we have to handle * @return <tt>true</tt> if the specified event has been handled by this * processor and shouldn't be offered to other processors registered * for the same method; <tt>false</tt>, otherwise */ public boolean processTransactionTerminated( TransactionTerminatedEvent transactionTerminatedEvent) { //we do nothing logger.info("Transaction Terminated :" + transactionTerminatedEvent); return false; } /** * Just log the end of the dialog * * @param dialogTerminatedEvent the event we have to handle * @return <tt>true</tt> if the specified event has been handled by this * processor and shouldn't be offered to other processors registered * for the same method; <tt>false</tt>, otherwise */ public boolean processDialogTerminated( DialogTerminatedEvent dialogTerminatedEvent) { //we do nothing logger.info("Dialog Terminated :" + dialogTerminatedEvent); return false; } /** * Sends the <tt>DTMFTone</tt> <tt>tone</tt> to <tt>callParticipant</tt>. * * @param callParticipant the call participant to send <tt>tone</tt> to. * @param tone the DTMF tone to send to <tt>callParticipant</tt>. * * @throws OperationFailedException with code OPERATION_NOT_SUPPORTED if * DTMF tones are not supported for <tt>callParticipant</tt>. * * @throws NullPointerException if one of the arguments is null. * * @throws IllegalArgumentException in case the call participant does not * belong to the underlying implementation. */ public void sendDTMF(CallParticipant callParticipant, DTMFTone tone) throws OperationFailedException, NullPointerException, IllegalArgumentException { if (callParticipant == null || tone == null) { throw new NullPointerException(); } if (! (callParticipant instanceof CallParticipantSipImpl)) { throw new IllegalArgumentException(); } CallParticipantSipImpl cp = (CallParticipantSipImpl) (callParticipant); this.sayInfo(cp, tone.getValue()); } }