/*
* 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.CallEventListener;
import com.sun.voip.TreatmentManager;
import com.sun.voip.Logger;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.NoSuchElementException;
import java.text.ParseException;
import java.util.Vector;
import java.util.Collection;
import org.voicebridge.Application;
import org.voicebridge.Config;
import org.ifsoft.rayo.RayoComponent;
/**
* Handle an incoming call. The call is placed into a temporary conference.
* Based on dtmf (or voice) input from the caller, the call is transferred
* to the target conference.
*
* This is a separate thread so that it can monitor the call status.
*/
public class IncomingCallHandler extends CallHandler
implements CallEventListener {
private Integer stateChangeLock = new Integer(0);
private ConferenceManager newConferenceManager;
private TreatmentManager treatmentManager;
private Object requestEvent;
boolean haveIncomingConferenceId = false;;
private static String defaultIncomingConferenceId = "IncomingCallsConference";
private static String incomingCallTreatment;
private static boolean incomingCallVoiceDetection = false;
private static boolean directConferencing = false;
private IncomingConferenceHandler incomingConferenceHandler;
public IncomingCallHandler(CallEventListener listener,
CallParticipant cp) {
this(listener, cp, null);
}
public IncomingCallHandler(CallParticipant cp, Object requestEvent) {
this(null, cp, requestEvent);
}
public IncomingCallHandler(CallEventListener listener, CallParticipant cp, Object requestEvent)
{
System.out.println("IncomingCallHandler " + cp);
if (CallHandler.enablePSTNCalls() == false) {
Logger.println("Ignoring incoming call " + cp.getToPhoneNumber());
return;
}
if (listener != null) {
addCallEventListener(listener);
}
this.cp = cp;
this.requestEvent = requestEvent;
addCallEventListener(this);
if (directConferencing)
{
if (cp.getConferenceId() == null || cp.getConferenceId().length() == 0)
{
System.out.println("Don't have conf, using default....");
cp.setConferenceId(defaultIncomingConferenceId); // wait in lobby
} else {
Logger.println("Have conf " + cp.getConferenceId());
haveIncomingConferenceId = true; // goto your conference
}
start();
} else {
System.out.println("Incoming SIP, call " + cp);
if (RayoComponent.self.routeIncomingSIP(cp))
{
haveIncomingConferenceId = true;
start();
} else {
// conf bridge
if (Config.getInstance().getConferenceExten().equals(cp.getToPhoneNumber()))
{
incomingConferenceHandler = new IncomingConferenceHandler(this, cp.getToPhoneNumber());
start();
} else if (Config.getInstance().getConferenceByPhone(cp.getToPhoneNumber()) != null) {
incomingConferenceHandler = new IncomingConferenceHandler(this, cp.getToPhoneNumber());
start();
} else {
cancelRequest(cp.getToPhoneNumber() + " is not a valid endpoint"); // reject call
}
}
}
}
public static void setDirectConferencing(boolean directConferencing) {
IncomingCallHandler.directConferencing = directConferencing;
}
public static boolean getDirectConferencing() {
return directConferencing;
}
public void cancelRequest(String s)
{
super.cancelRequest(s);
CallHandler otherCall = this.otherCall;
this.otherCall = null;
if (otherCall != null) {
Logger.println("otherCall is " + otherCall.getCallParticipant());
otherCall.cancelRequest("Bridged Call ended");
}
}
class TransferTimer extends Thread
{
private ConferenceMember member;
private String conferenceId;
private static final int TRANSFER_TIMEOUT = 3 * 60 * 1000;
public TransferTimer(ConferenceMember member)
{
this.member = member;
conferenceId = member.getCallParticipant().getConferenceId();
start();
}
public void run() {
try {
Thread.sleep(TRANSFER_TIMEOUT);
} catch (InterruptedException e) {
}
if (!done && member != null)
{
if (member.getCallParticipant().getConferenceId().indexOf(defaultIncomingConferenceId) == 0) {
Logger.println("Incoming call " + member + " call transfer timedout");
cancelRequest("Incoming call wasn't transferred");
}
}
}
}
/*
* Thread to process this incoming call.
* Create a temporary conference and add this call.
*/
public void run()
{
if (haveIncomingConferenceId == false) {
cp.setConferenceId(defaultIncomingConferenceId);
}
synchronized (ConferenceManager.getConferenceList())
{
conferenceManager =
ConferenceManager.getConference(cp.getConferenceId());
if (conferenceManager == null) {
Logger.error("Couldn't start conference "
+ cp.getConferenceId());
sendCallEventNotification(new CallEvent(CallEvent.CANT_START_CONFERENCE));
return;
}
cp.setDisplayName(cp.getName());
cp.setDtmfDetection(true);
if (cp.getCallId() == null) {
cp.setCallId(getNewCallId());
}
cp.setCallAnsweredTreatment(incomingCallTreatment);
cp.setVoiceDetection(incomingCallVoiceDetection);
if (haveIncomingConferenceId == false) {
cp.setWhisperGroupId(cp.getCallId());
cp.setMuted(true);
}
if (Logger.logLevel >= Logger.LOG_INFO) {
Logger.println(cp.getCallSetupRequest());
}
try {
member = conferenceManager.joinConference(cp);
memberSender = member.getMemberSender();
memberReceiver = member.getMemberReceiver();
} catch (IOException e) {
CallEvent callEvent =
new CallEvent(CallEvent.CANT_CREATE_MEMBER);
callEvent.setInfo(e.getMessage());
sendCallEventNotification(callEvent);
return;
}
Logger.println("Incoming Call " + cp + " joined conference "
+ cp.getConferenceId());
}
addCall(this); // add to list of active calls
String protocol = Bridge.getDefaultProtocol();
if (cp.getProtocol() != null) {
protocol = cp.getProtocol();
}
if (protocol.equalsIgnoreCase("NS")) {
csa = new NSIncomingCallAgent(this);
try {
csa.initiateCall();
} catch (IOException e) {
Logger.println("initiateCall failed: " + e.getMessage());
CallEvent callEvent =
new CallEvent(CallEvent.CANT_CREATE_MEMBER);
callEvent.setInfo(e.getMessage());
sendCallEventNotification(callEvent);
return;
}
} else if (protocol.equalsIgnoreCase("SIP")) {
csa = new SipIncomingCallAgent(this, requestEvent);
} else {
// XXX csa = new H323Agent(this);
Logger.println("H.323 support isn't implemented yet!");
sendCallEventNotification(new CallEvent(CallEvent.H323_NOT_IMPLEMENTED));
return;
}
if (haveIncomingConferenceId == false) {
new TransferTimer(member);
}
synchronized(stateChangeLock)
{
if (csa.getState() != CallState.ENDED)
{
try {
Logger.println("Call " + cp + " Waiting for call to end...");
stateChangeLock.wait(); // wait for call to end
} catch (InterruptedException e) {
}
}
}
try {
Logger.println("Call " + cp + " ended...");
conferenceManager.leave(member); // Remove member from conference.
} catch (Exception e) {
e.printStackTrace();
}
Logger.println("Call " + cp + " removed...");
removeCall(this); // remove from list of active calls
csa = null;
cancelRequest("Incoming call ended");
done = true;
}
public static void setIncomingCallTreatment(String treatment) {
incomingCallTreatment = treatment;
}
public static String getIncomingCallTreatment() {
return incomingCallTreatment;
}
public static void setIncomingCallVoiceDetection(
boolean incomingCallVoiceDetection) {
IncomingCallHandler.incomingCallVoiceDetection =
incomingCallVoiceDetection;
}
public static boolean getIncomingCallVoiceDetection() {
return incomingCallVoiceDetection;
}
private String lastDtmfKey = "";
private boolean speakDtmf = false;
public void callEventNotification(CallEvent callEvent) {
Logger.println("IncomingCallHandler " + callEvent.toString());
if (callEvent.equals(callEvent.STATE_CHANGED) &&
callEvent.getCallState().equals(CallState.ESTABLISHED)) {
if (incomingCallTreatment != null) {
try {
playTreatmentToCall(incomingCallTreatment);
} catch (IOException e) {
Logger.println(
"Unable to play incomingCallTreatment "
+ incomingCallTreatment);
}
}
} else if (callEvent.equals(CallEvent.DTMF_KEY)) {
member.stopTreatment(null);
String dtmf = callEvent.getDtmfKey();
if (lastDtmfKey.equals("*") && dtmf.equals("*")) {
speakDtmf = !speakDtmf;
}
lastDtmfKey = dtmf;
if (speakDtmf == true) {
speakDtmf(dtmf);
}
} else if (callEvent.equals(callEvent.STATE_CHANGED) && callEvent.getCallState().equals(CallState.ENDED)) {
Logger.println("Call " + cp + " Got ENDED status.");
synchronized(stateChangeLock) {
stateChangeLock.notify(); // the call has ended
}
}
Application.incomingCallNotification(callEvent);
}
private void speakDtmf(String dtmf) {
for (int i = 0; i < dtmf.length(); i++) {
String s = dtmf.substring(i, i + 1);
try {
if (s.equals("1")) {
playTreatmentToCall("1.au");
} else if (s.equals("2")) {
playTreatmentToCall("2.au");
} else if (s.equals("3")) {
playTreatmentToCall("3.au");
} else if (s.equals("4")) {
playTreatmentToCall("4.au");
} else if (s.equals("5")) {
playTreatmentToCall("5.au");
} else if (s.equals("6")) {
playTreatmentToCall("6.au");
} else if (s.equals("7")) {
playTreatmentToCall("7.au");
} else if (s.equals("8")) {
playTreatmentToCall("8.au");
} else if (s.equals("9")) {
playTreatmentToCall("9.au");
} else if (s.equals("0")) {
playTreatmentToCall("0.au");
} else if (s.equals("*")) {
playTreatmentToCall("star.au");
} else if (s.equals("#")) {
playTreatmentToCall("pound.au");
}
} catch (IOException e) {
Logger.println("Unable to play dtmf treatment " + s);
}
}
}
public String getNumberOfCallsAsTreatment() {
return getNumberOfCallsAsTreatment(getNumberOfCalls());
}
public String getNumberOfCallsAsTreatment(int n) {
String s;
if (n < 100) {
if (n < 20 || (n < 100 && (n % 10) == 0)) {
s = n + ".au";
} else {
int r = n % 10;
s = (n - r) + ".au;" + r + ".au";
}
} else if (n < 1000) {
int r = n % 100;
int q = n / 100;
if (r == 0) {
s = (n / 100) + ".au";
} else {
s = q + ".au;hundred.au;" + getNumberOfCallsAsTreatment(r);
}
} else {
s = "tts:" + n;
}
return s;
}
public ConferenceManager transferCall(String conferenceId)
throws IOException {
System.out.println("transferCall " + conferenceId);
ConferenceManager conferenceManager = transferCall(this, conferenceId);
String s = getNumberOfCallsAsTreatment(conferenceManager.getNumberOfMembers());
playTreatmentToCall("you-are-caller-number.au;" + s);
setMuted(false);
playTreatmentToConference("joinCLICK.au");
return conferenceManager;
}
public static ConferenceManager transferCall(String callId, String conferenceId) throws NoSuchElementException, IOException
{
CallHandler callHandler = CallHandler.findCall(callId);
if (callHandler == null) {
throw new NoSuchElementException("No such call: " + callId);
}
if (callHandler instanceof IncomingCallHandler == false) {
throw new NoSuchElementException("Only incoming calls can be transferred: " + callId);
}
return ((IncomingCallHandler)callHandler).transferCall(conferenceId);
}
public static ConferenceManager transferCall(CallHandler callHandler, String conferenceId) throws IOException
{
/*
* Get current conference manager and member.
*/
ConferenceMember member = callHandler.getMember();
ConferenceManager conferenceManager =
callHandler.getConferenceManager();
ConferenceManager newConferenceManager =
ConferenceManager.getConference(conferenceId);
if (newConferenceManager == null) {
throw new NoSuchElementException("Can't create conference "
+ conferenceId);
}
CallParticipant cp = callHandler.getCallParticipant();
//
// XXX maybe we should have yet more commands to specify
// the treatments for incoming calls.
// Jon suggested a mode for setting up incoming call parameters
// similar to how call setup is done for outgoing calls.
//
//cp.setConferenceJoinTreatment("joinCLICK.au");
//cp.setConferenceLeaveTreatment("leaveCLICK.au");
cp.setConferenceId(conferenceId);
conferenceManager.transferMember(newConferenceManager, member);
callHandler.setConferenceManager(newConferenceManager);
try {
newConferenceManager.addTreatment("joinCLICK.au");
} catch (IOException e) {
Logger.println("Call " + cp
+ " unable to play joinCLICK.au " + e.getMessage());
}
CallEvent event = new CallEvent(CallEvent.CALL_TRANSFERRED);
event.setInfo("ConferenceReceiverPort='"
+ callHandler.getReceiveAddress().getPort() + "'"
+ " ConferencePayload='"
+ newConferenceManager.getMediaInfo().getPayload() + "'"
+ " BridgeIPAddress='"
+ Bridge.getPrivateHost() + "'");
callHandler.sendCallEventNotification(event);
Application.notifyConferenceMonitors(event); // now, we monitor direct conferences as we know conf
return newConferenceManager;
}
public void playTreatmentToConference(String treatment) {
if (newConferenceManager == null) {
return;
}
try {
newConferenceManager.addTreatment("joinCLICK.au");
} catch (IOException e) {
Logger.println("Call " + this
+ " unable to play treatment " + treatment
+ " " + e.getMessage());
}
}
public TreatmentManager playTreatmentToCall(String treatment)
throws IOException {
treatmentManager = super.playTreatmentToCall(treatment);
return treatmentManager;
}
public CallEventListener getRequestHandler() {
return null;
}
public String toString() {
return super.toString();
}
}