/* * Copyright 2007 Sun Microsystems, Inc. * * This file is part of jVoiceBridge. * * jVoiceBridge is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation and distributed hereunder * to you. * * jVoiceBridge is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * Sun designates this particular file as subject to the "Classpath" * exception as provided by Sun in the License file that accompanied this * code. */ package com.sun.voip.server; import com.sun.voip.CallParticipant; import com.sun.voip.CallState; import com.sun.voip.CallEvent; import com.sun.voip.Logger; import com.sun.voip.MediaInfo; import com.sun.voip.TreatmentDoneListener; import com.sun.voip.TreatmentManager; import java.io.IOException; import java.net.InetSocketAddress; import java.text.ParseException; import java.util.NoSuchElementException; /** * Super class with code common to both Sip User Agents and * non-SIP Agents. * * This class is responsible for playing treatments, getting notification * when done, managing the state of active calls. */ public class CallSetupAgent implements TreatmentDoneListener, Runnable { protected TreatmentManager callAnsweredTreatment = null; private TreatmentManager callEstablishedTreatment = null; protected String reasonCallTerminated; protected CallState callState = new CallState(); protected CallHandler callHandler; protected CallParticipant cp; private Thread inviteTimeoutThread; private static int defaultCallAnswerTimeout = 90; // 90 seconds /** * Constructor * @param callHandler CallHandler this CallParticipant is associated with * @param cp the CallParticipant */ public CallSetupAgent(CallHandler callHandler) { this.callHandler = callHandler; cp = callHandler.getCallParticipant(); } /* * INVITE timeout thread, handle call not answered */ public void run() { int timeout = cp.getCallAnswerTimeout(); if (timeout == 0) { timeout = defaultCallAnswerTimeout; } try { Thread.sleep(timeout * 1000); } catch (InterruptedException e) { } inviteTimeoutThread = null; if (reasonCallTerminated == null && getState() < CallState.ANSWERED) { Logger.println("Call answer time out " + cp); sendCallEventNotification( new CallEvent(CallEvent.CALL_ANSWER_TIMEOUT)); cancelRequest("No answer"); } } /** * set the call answer timeout in seconds */ public static void setDefaultCallAnswerTimeout( int defaultCallAnswerTimeout) { CallSetupAgent.defaultCallAnswerTimeout = defaultCallAnswerTimeout; } /** * Get the call answer timeout in seconds */ public static int getDefaultCallAnswerTimeout() { return defaultCallAnswerTimeout; } public CallState getCallState() { return callState; } public int getState() { return callState.getState(); } protected void setState(int state) { setState(state, null); } protected void setState(int state, String info) { callState = new CallState(state); CallEvent callEvent = new CallEvent(CallEvent.STATE_CHANGED); callEvent.setCallState(callState); String s = ""; if (state == CallState.INVITED || state == CallState.ESTABLISHED) { s += "ConferenceReceiverPort='" + callHandler.getReceiveAddress().getPort() + "'"; MediaInfo mediaInfo = callHandler.getConferenceManager().getMediaInfo(); s += " ConferencePayload='" + mediaInfo.getPayload() + "'"; s += " BridgeIPAddress='" + Bridge.getPrivateHost() + "'"; s += " BridgeInfo='" + Bridge.getPrivateHost() + ":" + Bridge.getPrivateControlPort() + ":" + Bridge.getPrivateSipPort() + ":" + Bridge.getPublicHost() + ":" + Bridge.getPublicControlPort()+ ":" + Bridge.getPublicSipPort() + "'"; } if (info != null) { s = info + " " + s; } callEvent.setInfo(s); Logger.println("Call " + callHandler + " " + callState); sendCallEventNotification(callEvent); if (state == CallState.ESTABLISHED) { String treatment = cp.getCallEstablishedTreatment(); if (treatment != null) { callEstablishedTreatment = initializeTreatment(treatment, 0); if (callEstablishedTreatment != null) { addTreatment(callEstablishedTreatment); } } } if (inviteTimeoutThread == null && state == CallState.INVITED) { inviteTimeoutThread = new Thread(this); inviteTimeoutThread.start(); // start invite timeout thread } } protected void sendCallEventNotification(CallEvent callEvent) { callHandler.sendCallEventNotification(callEvent); } /* * Send the address of the endpoint to the CallHandler so * the CallHandler can tell the Mixer where to send RTP data. */ protected void setEndpointAddress(InetSocketAddress isa, byte mediaPayload, byte receivePayload, byte telephoneEventPayload) { setEndpointAddress(isa, mediaPayload, receivePayload, telephoneEventPayload, null); } protected void setEndpointAddress(InetSocketAddress isa, byte mediaPayload, byte receivePayload, byte telephoneEventPayload, InetSocketAddress rtcpAddress) { callHandler.setEndpointAddress(isa, mediaPayload, receivePayload, telephoneEventPayload, rtcpAddress); } /* * true if call is established */ public boolean isCallEstablished() { return getState() == CallState.ESTABLISHED || getState() == CallState.ENDING; } public boolean isCallEnding() { return getState() == CallState.ENDING || getState() == CallState.ENDED; } /** * Setup the call answerered treatment manager */ protected void initializeCallAnsweredTreatment() { String treatment = cp.getCallAnsweredTreatment(); int repeatCount = 0; if (cp.getJoinConfirmationTimeout() != 0) { /* * If join confirmation is requested, we will repeat * the message. This is needed because AccessLine * answers the call before a person actually gets connected * to hear the message. */ repeatCount = 30; } callAnsweredTreatment = initializeTreatment(treatment, repeatCount); if (callHandler.isFirstMember()) { treatment = cp.getFirstConferenceMemberTreatment(); if (treatment != null) { callAnsweredTreatment = initializeTreatment(treatment, repeatCount); } } } private TreatmentManager initializeTreatment(String treatment, int repeatCount) { MediaInfo mediaInfo = callHandler.getConferenceManager().getMediaInfo(); if (treatment != null) { try { return new TreatmentManager( treatment, repeatCount, mediaInfo.getSampleRate(), mediaInfo.getChannels()); } catch (IOException e) { Logger.println("can't play treatment " + treatment + " " + e.getMessage()); } } return null; } /* * Add a treatment to play to the call. */ protected void addTreatment(TreatmentManager treatmentManager) { callHandler.addTreatment(treatmentManager); } /** * Start call answered treatment */ protected void startCallAnsweredTreatment() { Logger.println("Call " + callHandler + " starting call answered treatment"); callAnsweredTreatment.addTreatmentDoneListener(this); callHandler.addTreatment(callAnsweredTreatment); } /** * Stop call answered treatment */ public void stopCallAnsweredTreatment() { if (callAnsweredTreatment != null) { if (Logger.logLevel >= Logger.LOG_MOREINFO) { Logger.println("Call " + callHandler + " Stop callAnsweredTreatment player..."); } callAnsweredTreatment.stopTreatment(); callAnsweredTreatment = null; } else { if (cp.getJoinConfirmationTimeout() != 0) { if (getState() == CallState.INVITED) { setState(CallState.ANSWERED); MediaInfo mediaInfo = callHandler.getConferenceManager().getMediaInfo(); setState(CallState.ESTABLISHED, "ConferencePayload='" + mediaInfo.getPayload() + "'" + " BridgeIPAddress='" + Bridge.getPrivateHost() + "'"); } } } } public void stopCallEstablishedTreatment() { if (callEstablishedTreatment == null) { return; } if (Logger.logLevel >= Logger.LOG_MOREINFO) { Logger.println("Call " + callHandler + " Stop callEstablishedTreatment player..."); } callEstablishedTreatment.stopTreatment(); callEstablishedTreatment = null; } /** * Notification that a treatment has finished * * The state we are in determines what we do next. */ public void treatmentDoneNotification(TreatmentManager treatmentManager) { if (Logger.logLevel >= Logger.LOG_MOREINFO) { Logger.println("Call " + callHandler + " treatment done notification, current state " + callState + " " + callHandler); } switch (getState()) { case CallState.INVITED: /* * When join confirmation is requested, we stay in the * CALL_PARTICIPANT_INVITED state until a dtmf key is pressed. * Then we change state to CALL_PARTICIPANT_ANSWERED and * fall through. * * XXX We need to make sure the treatment repeats enough times. * If it finishes before we timeout, we'll treat the call as answered! * */ if (reasonCallTerminated != null) { break; } setState(CallState.ANSWERED); case CallState.ANSWERED: /* * Call answered treatment is done, we're ready for the conference */ MediaInfo mediaInfo = callHandler.getConferenceManager().getMediaInfo(); setState(CallState.ESTABLISHED, "ConferencePayload='" + mediaInfo.getPayload() + "'" + " BridgeIPAddress='" + Bridge.getPrivateHost() + "'"); break; case CallState.ENDING: /* * Call end treatment is done, time to end the call */ terminateCall(); done(); break; default: Logger.error("Call " + callHandler + ": unexpected state " + callState); break; } } /** * A CallSetupAgent may override the following methods: * * initiateCall() to start a call * terminateCall() class specific code to end a call. */ public void initiateCall() throws IOException { } public String getSdp() { return null; } public void setRemoteMediaInfo(String sdp) throws ParseException { } public void terminateCall() { } /** * Cancel a call */ public void cancelRequest(String s) { if (reasonCallTerminated != null || getState() == CallState.ENDED) { return; } reasonCallTerminated = s; if (inviteTimeoutThread != null) { inviteTimeoutThread.interrupt(); inviteTimeoutThread = null; } if (Logger.logLevel >= Logger.LOG_INFO) { Logger.println( "Call " + callHandler + ": cancelling call, " + s); } if (callAnsweredTreatment != null) { callAnsweredTreatment.stopTreatment(); } if (callEstablishedTreatment != null) { callEstablishedTreatment.stopTreatment(); } if (getState() == CallState.ESTABLISHED) { String endTreatment = cp.getCallEndTreatment(); if (endTreatment != null && cp.isConferenceMuted() == false) { try { MediaInfo mediaInfo = callHandler.getConferenceManager().getMediaInfo(); TreatmentManager callEndTreatment = new TreatmentManager(endTreatment, 0, mediaInfo.getSampleRate(), mediaInfo.getChannels()); setState(CallState.ENDING, "Reason='" + s + "'"); if (Logger.logLevel >= Logger.LOG_MOREINFO) { Logger.println("Call " + callHandler + " adding end treatment..."); } callEndTreatment.addTreatmentDoneListener(this); callHandler.addTreatment(callEndTreatment); return; } catch (IOException e) { Logger.error("Call " + callHandler + " " + e.getMessage()); } } } terminateCall(); // do subclass specific work setState(CallState.ENDING, "Reason='" + s + "'"); done(); } /* * Finish the call termination process */ private void done() { if (getState() == CallState.ENDED) { return; } setState(CallState.ENDED, "Reason='" + reasonCallTerminated + "'"); } }