Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Home

Stratos Kalogirou edited this page · 10 revisions
Clone this wiki locally

The VoIP Example shows how to create a simple VoIP client MIDlet for making internet calls over SIP protocol. It demonstrates the usage the VoIP API on Nokia Asha software platform 1.1 and how to handle VoIP calls.

 

Design

The VoIP Example application is a typical voice call application for making and receiving VoIP calls, having also an address book and a call log.

The basic functionality is divided on three tab-based views: Contacts, Dialer and Call log. In addition there are views for an actual active call, for an incoming call and for the SIP account settings. The UI is implemented using LCDUI components.

Contacts

 

The Contacts view lists all the saved contacts in the address book of the application. Contact information can be added and modified in a subview, as well as making a VoIP call to the selected contact.

Dialer

The Dialer view provides a simple phone number dialer. Depending on the used SIP service provider, phone calls may be made to the public telephone network as well by using a normal phone number. Calling to a VoIP contact with a SIP address, requires adding it to the address book instead.

Call log

 

The Call log view presents the call history, grouped in three subcategories: received calls, dialed calls and missed calls. Caller name and a time stamp is shown on the listing. If the caller is not found from the address book, a VoIP address is shown instead of the real name.

Settings

The Settings view contains details of the used SIP account: username, password and registrar server. In addition, a custom ringtone file can be selected instead of the default one.

Incoming VoIP call

The Incoming VoIP call view informs about an incoming call with ability to answer or reject the call. At the same time, a ringtone is played. The SIP address of the caller is showed as well as the real name of the person, if it can be resolved from the local address book.

VoIP call

 

The VoIP call view shows an active call. In addition to the call ending button, there are toggle buttons for muting, putting the call on hold and switching the speaker mode. Furthermore, there is a subview for sending DTMF tones, needed mostly in many phone services.

Implementation

The example assumes that you have a working SIP account in order to use the application. You can register a free SIP account from many public VoIP service providers. There are also free VoIP server implementations for setting up your own SIP service, but installing and configuring such is out of the scope of this documentation.

VoIP settings

As the platform is taking care most of the actual VoIP functionality, it needs also the specific VoIP settings from the given application and SIP account. The VoIP settings of each VoIP MIDlets are given in a form of an XML file to the VoIP API. The complete voip_settings.xml file is found from the project resource files, but below is a short code snippet to demonstrate in which form the SIP account details are given. In addition to the SIP account details, there are lots of other VoIP settings, like network and audio codec settings, that are not covered in the documentation of this example. In most cases, a major part of the settings can be left to their default values. More information about the VoIP settings XML file format can be found from the VoIP API documentation.

#!xml
<?xml version="1.0"?>
<!DOCTYPE wap-provisioningdoc PUBLIC "-//WAPFORUM//DTD PROV 1.0//EN"
"http://www.wapforum.org/DTD/prov.dtd">
<wap-provisioningdoc version="1.1">

  ...

  <!-- SIP settings, w9010 -->
  <characteristic type="APPLICATION">
    <parm name="APPID" value="w9010"/>
    <parm name="APPREF" value="Nokia_VoIP_example_settings"/>
    <parm name="PROVIDER-ID" value="Nokia VoIP example"/>
    <parm name="TO-NAPID" value="INTERNET"/>
    <parm name="PTYPE" value="IETF"/>
    <parm name="PUID" value="sip:<!-- SIP_USERNAME -->@<!-- SIP_REGISTRAR -->"/>
    <parm name="APROTOCOL" value="UDP" />  
    <!-- Outbound settings -->
    <characteristic type="APPADDR">
      <parm name="LR"/>
      <parm name="ADDR" value="<!-- SIP_REGISTRAR -->"/>
      <characteristic type="PORT">
        <parm name="PORTNBR" value="5060" /> 
      </characteristic>
    </characteristic>   
    <!-- Outbound credentials -->
    <characteristic type="APPAUTH">
      <parm name="AAUTHNAME" value="<!-- SIP_USERNAME -->"/>
      <parm name="AAUTHSECRET" value="<!-- SIP_PASSWORD -->"/>
      <parm name="AAUTHDATA" value=""/>
      <parm name="AAUTHTYPE" value="HTTP-DIGEST"/>
    </characteristic>   
    <!-- Registrar location and credentials -->
    <characteristic type="RESOURCE">
      <parm name="URI" value="<!-- SIP_REGISTRAR -->"/>
      <parm name="AAUTHNAME" value="<!-- SIP_USERNAME -->"/>
      <parm name="AAUTHSECRET" value="<!-- SIP_PASSWORD -->"/>
      <parm name="AAUTHDATA" value=""/>
      <parm name="AAUTHTYPE" value="HTTP-DIGEST" /> 
    </characteristic>
  </characteristic>
</wap-provisioningdoc>

As can be seen from the previous code snippet, there are placeholders XML comments for the SIP username, password and registrar server address. These placeholders are replaced with the user SIP account settings by the MIDlet and the given to the VoIP API.

SettingsHelper class is encapsulating the settings handling with the VoIP API in this VoIP Example MIDlet. It creates a VoipSettings object, returned by VoipManager class, and uses writeVoipSettings method to write the complete XML settings string to the VoIP API. Before that, it reads the predefined settings XML file from the resources of the MIDlet and replaces the aforementioned placeholder comments in the XML with the actual user SIP account details, specified in the Settings view, in insertUserSettings helper method.

public class SettingsHelper {

    private VoipSettings voipSettings;
    private SettingsStateListener settingsStateListener;
    private Settings settings;
    private SettingsManager settingsManager;

    public SettingsHelper(SettingsManager settingsManager) {
        this.settingsManager = settingsManager;
        voipSettings = VoipManager.createVoipSettingsInstance();
        settingsStateListener = new SettingsStateListener();
        voipSettings.setVoipSettingsStateListener(settingsStateListener);
    }

    public void writeSettings()
        throws IOException {
        String settingsXml = null;
        settings = settingsManager.getSettings();

        settingsXml = readSettingsFile("/voip_settings.xml");
        settingsXml = insertUserSettings(
            settingsXml,
            settings.getUsername(),
            settings.getPassword(),
            settings.getRegistrar());
        voipSettings.writeVoipSettings(settingsXml);
    }

    ...
}

Handling of VoIP call instances

Call class encapsulates the functionality to handle and monitor VoIP call instances. Call class is actually a simple wrapper class to simplify the VoIP API even further for the needs of this VoIP Example MIDlet. It contains an instance of VoipAudioCall class to control the call instance over the VoIP API and implements VoipAudioCallStateListener interface class to monitor the state of the call. The most of the methods of Call class are simple call through methods to VoipAudioCall class with some additional logic. Call class also provides very simplified callback mechanism for UI views just to monitor the disconnection of the call via CallStateListener interface class.

public class Call
    implements VoipAudioCallStateListener {

    private VoipAudioCall call = null;
    private CallStateListener listener = null;
    private boolean callGotThrough = false;

    public void setCallStateListener(CallStateListener listener) {
        this.listener = listener;
    }

    public boolean isActive() {
        if (call == null || call.getCallState() == VoipCauses.CAUSE_DISCONNECTED) {
            return false;
        }

        return true;
    }

    ...

    public void makeCall(String voipAddress) {
        // Set up an outgoing call instance.
        call = VoipManager.createVoipAudioMOCallInstance(voipAddress);
        call.setVoipAudioCallStateListener(this);
        boolean showCallerId = VoIPExample.getInstance().getSettingsManager()
            .getSettings().isShowCallerId();
        call.startCall(showCallerId);
    }

    public void receiveCall(int callId) {
        // Set up an incoming call instance.
        call = VoipManager.createVoipAudioMTCallInstance(callId);
        call.setVoipAudioCallStateListener(this);
    }

    public void answer() {
        if (call != null) {
            call.startCall(true);
        }
    }

    public void reject() {
        if (call != null) {
            call.endCall();
        }
    }

    public void end() {
        if (call != null) {
            call.endCall();
        }
    }

    ...

    public void voipAudioCallUpdated(int state, int cause, int callId) {
        System.out.println("Call state: " + state + ", cause: " + cause
            + ", callId:" + callId);

        if (state == VoipStates.AUDIOCALL_ENDED || 
           (state == VoipStates.AUDIOCALL_CALL & cause == VoipCauses.CAUSE_DISCONNECTED)) {
            System.out.println("Call disconnected");
            callGotThrough = false;

            // Create a new thread to tear down the call instance and notify
            // the call state listener about disconnection after few seconds.
            new Thread() {
                public void run() {
                    try {
                        if (listener != null) {
                            listener.onCallDisconnected();
                        }
                        Thread.sleep(3000);
                        call.setVoipAudioCallStateListener(null);
                        call = null;
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }.start();
        }
    }
}

Outgoing VoIP calls

Making an outgoing VoIP call is simple. The method call in the main MIDlet class is used to launch an outgoing VoIP call. Based on the given VoIP address, a matching Contact object is searched, if available, and a call log entry is made. Crating a new Call object and calling makeCall method is enough to set up the call instance. After that CallView is opened.

    public void call(String voipAddress) {
        System.out.println("Outgoing call to: " + voipAddress);

        // Add a call log entry.
        Contact contact = contactsManager.findContact(voipAddress);
        if (contact == null) {
            contact = new Contact("", voipAddress);
        }
        callLogManager.addLogItem(voipAddress, CallLogItem.TYPE_DIALED);

        call = new Call();
        call.makeCall(voipAddress);

        viewManager.pushView(new CallView(viewManager, contact, call));
    }

Incoming VoIP calls

The MIDlet class of this VoIP Example implements VoipMTCallListener interface class in order to get notified about incoming VoIP calls via a callback method onIncomingCall. In order to achieve that, the MIDlet has to register itself as a listener of that callback to VoipManager class through setVoipMTCallListener method. The registration is done in the constructor of the MIDlet.

public class VoIPExample
    extends MIDlet
    implements VoipMTCallListener {

    ...

    public VoIPExample() {
        if (!VoipManager.setVoipMTCallListener(this)) {
            System.out.println("VoIP API not supported!");
        }
    }

    ...

    public void onIncomingCall(int callId) {
        // Handling of multiple simultaneous calls (one active & others on hold)
        // is not implemented in this example. However, the platform VoIP
        // service supports it, so the functionality can be extended to handle
        // multiple calls accordingly.
        if (call == null || !call.isActive()) {
            incomingCall(callId);
        } else {
            System.out.println("Incoming call ignored due to an active call");
        }
    }

    ...
}

In a case of an incoming VoIP call when the MIDlet is not already running, the platform VoIP service will launch the registered MIDlet. It will pass the invoke reason and the call ID of the incoming call instance as command line arguments to the MIDlet. Based on the given argument values, the MIDlet can show an incoming call view accordingly and set up the call.

    public void startApp() {
        ...
        // Check the argument values if the MIDlet was launched
        // by the platform VoIP service due to an incoming call.
        String invokeReason = this.getAppProperty("arg-0");
        if (invokeReason.equals(VoipManager.VOIP_MT_CALL_ALERTING)
            || invokeReason.equals(VoipManager.VOIP_MT_CALL_WAITING)) {
            System.out.println("MIDlet launched due to an incoming call");
            try {
                int callId = Integer.parseInt(this.getAppProperty("arg-1"));
                incomingCall(callId);
            } catch (NumberFormatException e) {
                System.out.println("Invalid call ID!");
            }
        }
        ...
    }

After getting a callback about an incoming VoIP call, the handling of it is very similar with outgoing VoIP calls. A new Call object is created and the call is set up by calling receiveCall method with the call instance ID. Playing the ringtone is started, which will be discussed a bit later, and Contact object is searched from the address book, if available, to show the real name of the caller. After that IncomingCallView is opened.

    public void incomingCall(int callId) {
        call = new Call();
        call.receiveCall(callId);
        ringtonePlayer.startRinging();

        // Find the caller from the saved contacts to show the name.
        String voipAddress = call.getAddress();
        Contact contact = contactsManager.findContact(voipAddress);
        if (contact == null) {
            contact = new Contact("", voipAddress);
        }

        System.out.println("Incoming call from: " + voipAddress);

        viewManager.pushView(new IncomingCallView(viewManager, contact, call));
    }

Playing ringtone

For playing a ringtone on incoming VoIP calls, there is RingtonePlayer class. An instance of it is stored by the MIDlet main class. It uses Player class of Mobile Media API to simply play a MP3 file in a loop as long as the playback is requested to stop.

public class RingtonePlayer {
    Player player;

    public void startRinging() {
        try {
            // Play the the default ringtone.
            InputStream stream = getClass()
                .getResourceAsStream("/ringtone.mp3");
            player = Manager.createPlayer(stream, "audio/mp3");
            player.setLoopCount(-1);
            player.start();
        } catch (Exception e) {
            System.out.println("Playing ringtone failed!");
            player = null;
        }
    }

    public void stopRinging() {
        if (player != null) {
            try {
                player.stop();
            } catch (MediaException e) {
                System.out.println("Stopping ringtone failed!");
            }
            player = null;
        }
    }
}

Displaying the call duration

The CallCounter class is used to determine when a call is connected to the remote recipient and display the call duration. A TimerTask is used for this purpose, that polls every second the call instance to check the status of the current call. If the call is not connected, only the speaker and the dialer options are displayed. For connected calls, the mute and hold options are also displayed. The TimerTask increases the call duration every second and then the covertDurationToString method is used to convert the call duration from seconds to hh:mm:ss format as shown below.

    public void run() {
        if(call.isActiveCall()) {
            duration++;  
            durationString = convertDurationToString(duration);
            callView.setCallDuration(durationString);
            callView.showMuteHold(true);
        }
        else {
            callView.showMuteHold(false);
        }
    }
    /*
     * Converts the call duration from seconds to
     * hh:mm:ss format 
     */
    private String convertDurationToString(int duration) {
        int remainingSeconds = duration % 60;
        int minutes = (duration - remainingSeconds) / 60;
        int remainingMinutes = minutes % 60;
        int remainingHours = (minutes - remainingMinutes) / 60;

        return formatTime(remainingHours) + ":" + 
               formatTime(remainingMinutes) + ":" + 
               formatTime(remainingSeconds);
    }
Something went wrong with that request. Please try again.