Implementing the TerminatingInvite MIDlet

The TerminatingInvite MIDlet creates a form for printing progress information. The form contains a field for the receiving port. The MIDlet also sets up the following commands: Online and Exit.

In the command handler for the MIDlet if the command is "Online" the following occurs:

In the command handler for the MIDlet if the command is "Exit" the MIDlet exits.

You can find more information in several topics by visiting these links:

Note: The SDP content delivered in the INVITE and 200 OK messages is not relevant for operation of this example application. It is just there as an example.

  1. Create the TerminatingInvite class file.

  2. Import the required classes and assign this class to the com.nokia.midp.examples.sip package.

    /* Copyright © 2006 Nokia. */
    package com.nokia.midp.examples.sip;
    
    
    import java.io.*;
    import javax.microedition.io.*;
    import javax.microedition.midlet.*;
    import javax.microedition.lcdui.*;
    import javax.microedition.sip.*;
    
    

    Create the variables needed for the connection.

    /**
     * TerminatingInvite: Implements a SIP messaging session listener via JSR-180.
     * It answers to SIP INVITE request by sending "180 Ringing" and "200 OK".  Stops
     * session by sending "BYE".  It uses shared mode when connecting to the sip server.
     * This example assumes that user already created the profile on the SDK's or
     * phone's sip setting, and uses sip profile to register with sip server.  The
     * sip public name for the SDK running originatingInvite is "sip:sip1@sipServer",
     * and the sip public name for the SDK running terminatingInvite is
     * "sip:sip2@sipServer".
     */
    public class TerminatingInvite extends MIDlet implements CommandListener, 
       SipServerConnectionListener  {
    
       private Display display;
       private long startTime;
       private Form form;
       private TextField receivePort;
       private Command startCmd;
       private Command restartCmd;
       private Command byeCmd;
       private Command answerCmd;
       private Command cancelCmd;
       private Command exitCmd;
       private boolean active = false;
       private SipDialog dialog;
       private StringItem str;
       private SipServerConnection ssc = null; // latest request
       private SipServerConnection origSSC = null; // original request
    
    

    In the code below, the values of the Variables define the SIP methods, various descriptive texts used to convey the progress of the session, the post information of both this Midlet and the other party and SipClientConnection's header information. You can read more about the SipConnectionNotifier from the JSR-180 specification. Also, an SDP similar to the one created in OriginatingInvite is defined here.

       //States
       private final short S_OFFLINE = 0;
       private final short S_CALLING = 1;
       private final short S_RINGING = 2;
       private final short S_ONLINE = 3;
    
       //Protocol constants - headers
       private final String CALL_ID_HEADER = "Call-ID";
       private final String ACCEPT_CONTACT_HEADER = "Accept-Contact";
    
       //Protocol constants - some header values
       private final String SDP_MIME_TYPE = "application/sdp";
    
       //Protocol constants - status
       private final int OK_STATUS = 200;
       private final int BUSY_STATUS = 486;
       private final int REQUEST_TERMINATED_STATUS = 487;
       private final int RING_STATUS = 180;
    
       //Protocol constants - methods
       private final String BYE_METHOD = "BYE";
    
       //Timeouts values
       private final int RECEIVE_TIMEOUT = 10000;
    
       //UI labels and messages:
       private final String HANG_UP = "user hang-up: ";
       private final String SESSION_CLOSE_NOTIFY = 
          "Session closed successfully...";
       private final String SESSION_CANCEL = "Session canceled: ";
       private final String SIP_PORT_LABEL = "SipConnectionNotifier on port:";
       private final String EXCEPTION = "Exception: ";
    
       private final String SIP_PORT = "sip:5060";
    
       private short state = S_OFFLINE;
    
       // using static SDP (Session Description Protocol) as an example
       private String sdp = "v=0\no=sippy 2890844730 2890844732 IN IP4 
          host.example.com\ns=example code\nc=IN IP4 host.example.com\nt=0 
          0\nm=message 54344 SIP/TCP\na=user:sippy";
       SipConnectionNotifier scn = null;
    
    
    
  3. Create a new TerminatingInvite object as shown below. Make sure it includes the initializing of the MIDlet display and creates a Form. Create and add commands to the form.

       /**
        * Creates a new TerminatingInvite object with UI for the user to
        * enter connection information.
        */
       public TerminatingInvite() {
          // Initialize MIDlet display
          display=Display.getDisplay(this);
          form = new Form("Session example");
          receivePort = new TextField(SIP_PORT_LABEL, SIP_PORT, 
             30, TextField.LAYOUT_LEFT);
          form.append("Click online to listen on the port.");
          answerCmd = new Command("Answer", Command.OK, 1);
          byeCmd = new Command("Hang-up", Command.CANCEL, 1);
          restartCmd = new Command("Restart", Command.OK, 1);
          startCmd = new Command("Online", Command.OK, 1);
          form.addCommand(startCmd);
          exitCmd = new Command("Exit", Command.EXIT, 2);
          form.addCommand(exitCmd);
          orm.setCommandListener(this);
    
       }
    
  4. Create a commandAction object to give functionality to the commands created in step 3. startCmd clears the form, initializes a new Thread t (that contains the method for calling startListener) and in turn calls it with the start method.

       /**
        * Creates a commandAction object with given command and displayable.
        * @param c Command the command used for the commandAction
        * @param d Displayable the displayable used when executing the command
        */
       public void commandAction(Command c, Displayable d) {
          if(c == startCmd) {
             form.deleteAll();
             form.removeCommand(startCmd);
    
             Thread t = new Thread() {
                public void run() {
                   startListener();
                }
             };
             t.start();
    
             return;
          }
    

    exitCmd will terminate the Midlet with the destroyApp method.

          else if(c == exitCmd) {
             if(scn != null) {
                try {
                   scn.close();
                }
                catch(IOException iox) {
                   form.append(EXCEPTION + iox.getMessage());
                }
             }
             destroyApp(true);
             return;
          }
    
    

    byeCmd can be used to either call the sendBYE method described later in the code or, in case the S_RINGING state is on, a "Busy here" response (486) is initialized and sent.

          else if(c == byeCmd) {
             if(state == S_RINGING) {
                try {
                   ssc.initResponse(BUSY_STATUS); // Busy here
                   ssc.send();
                   str = new StringItem("Session closed: ", "Busy here!");
                   form.append(str);
                } catch(Exception ex) {
                   ex.printStackTrace();
                   form.append(EXCEPTION + ex.getMessage());
                }
             } else {
                sendBYE();
             }
             form.removeCommand(byeCmd);
             form.removeCommand(answerCmd);
             form.addCommand(restartCmd);
             state = S_OFFLINE;
          }
    

    The answerCmd initializes the ssc instance with OK_STATUS (200). An OutputStream is initialized for receiving content, in this case the already defined sdp, which is then sent and saved. After this the wait for ACK from the other party begins, and the ssc is closed.

          else if(c == answerCmd) {
             form.removeCommand(answerCmd);
             form.addCommand(byeCmd);
             try {
                ssc.initResponse(OK_STATUS);
                ssc.setHeader("Content-Length", ""+sdp.length());
                ssc.setHeader("Content-Type", "application/sdp");
                OutputStream os = ssc.openContentOutputStream();
                os.write(sdp.getBytes());
                os.close(); // close and send
                // save Dialog
                dialog = ssc.getDialog();
                form.append("Dialog state: " + dialog.getState()+"\n");
    
                // Wait for otherside to ACK
                form.append("Waiting for ACK...");
             } catch(Exception ex) {
                ex.printStackTrace();
                form.append(EXCEPTION + ex.getMessage());
             }
          }
    

    The restartCmd will prepare the form for starting over.

          else if(c == restartCmd) {
             if(scn != null) {
                try {
                   scn.close();
                } catch(Exception ex) {
                   form.append(EXCEPTION + ex.getMessage());
                }
             }
                form.removeCommand(restartCmd);
                form.addCommand(startCmd);
                form.deleteAll();
                form.append(receivePort);
                return;
             }
          }
    
  5. Write the startApp method shown below, used to activate the MIDlet.

       /**
        * Signals the MIDlet that it has entered the Active state.  It also
        * invokes the application and makes the form visible.
        */
       public void startApp() {
          display.setCurrent(form);
       }
    
    
  6. Create a method containing the necessary information needed to start a listener for incoming requests. The methods setListener and getLocalPort come from SIP, and you can find more information about them in the JSR-180 document.

       private void startListener() {
    
          try {
             if(scn != null)
                scn.close();
                // start a listener for incoming request
                form.append("Waiting for the connection.");
                scn = (SipConnectionNotifier) Connector.open("sip:*;type=\"" 
                   + SDP_MIME_TYPE + '"');
                scn.setListener(this);
                form.append("Listening on port: "+scn.getLocalPort());
          } catch(Exception ex) {
             ex.printStackTrace();
             form.append(EXCEPTION + ex.getMessage());
          }
       }
    
    
  7. Get the listening point for the ssc instance from scn with the acceptAndOpen method. If ssc receives an INVITE, the content of origSSC is replaced with that of the ssc to preserve it. The state is set to S_CALLING.

       /**
        * Processes the requests based on contents.
        * Responds with 200 OK after getting the messages.
        * @param scn SipConnectionNotifier the SIP server connection notifier
        * object
        */
       public void notifyRequest(SipConnectionNotifier scn) {
          try {
             ssc = scn.acceptAndOpen(); // blocking
                if(ssc.getMethod().equals("INVITE")) {
                   origSSC = ssc;
                   state = S_CALLING;
                   // handle content sdp
    

    The content of ssc should be handled next, transferring it to the prepared variables.

                   String contentType = ssc.getHeader("Content-Type");
                   String contentLength = ssc.getHeader("Content-Length");
                   int length = Integer.parseInt(contentLength);
    
                   if(contentType.equals("application/sdp")) {
                      InputStream is = ssc.openContentInputStream();
                      byte content[] = new byte[length];
                      is.read(content);
                      String sc = new String(content);
                      int m = sc.indexOf("m=");
                      String media = sc.substring(m, sc.indexOf('\n', m));
                      str = new StringItem("media is: ", media);
                      form.append(str);
    

    After the ssc has been handled, it is initialized with the RING_STATUS (180) variable and sent back. The form is appended with the text "RINGING!!!" and the state is set to S_RINGING.

                      // initialize and send 180 response
                      ssc.initResponse(RING_STATUS);
                      ssc.send();
                      // save Dialog
                      dialog = ssc.getDialog();
                      form.append("Dialog state: " + dialog.getState()+"\n");
                      form.addCommand(answerCmd);
                      form.addCommand(byeCmd);
                      // inform user about the session here...
                      form.append("RINGING!!!\n");
                      state = S_RINGING;
                      return;
                   }
    
                }
    

    If ssc receives an ACK instead of the INVITE, the state is set to S_ONLINE. The MIDlet starts waiting for the BYE method.

                else if(ssc.getMethod().equals("ACK")) {
                   str = new StringItem("Session established: ", 
                      ssc.getHeader(CALL_ID_HEADER));
                   state = S_ONLINE;
                   form.append(str);
                   dialog = ssc.getDialog();
                   form.append("Dialog state: " + dialog.getState()+"\n");
                   // Wait for otherside to send BYE
                   form.append("Waiting for BYE...");
                }
             }
    

    When a BYE is received, a response is initialized with OK_STATUS (200) and sent back. The state is changed to S_OFFLINE. The ssc is closed and form is changed to be ready for restarting.

             else if(ssc.getMethod().equals(BYE_METHOD)) {
                ssc.initResponse(OK_STATUS);
                ssc.send();
                state = S_OFFLINE;
                str = new StringItem("Session closed: ", 
                   ssc.getHeader(CALL_ID_HEADER));
                form.append(str);
                form.append("Dialog state: " + dialog.getState());
                ssc.close();
                form.removeCommand(byeCmd);
                form.removeCommand(answerCmd);
                form.addCommand(restartCmd);
    

    When ssc receives a CANCEL SIP method, a response is initialized with OK_STATUS (200) and sent back. origSSC is initialized with Request Terminated( 487) and sent. The state variable is changed to S_OFFLINE, the ssc is closed, and form is changed to be ready for restarting.

             }else if(ssc.getMethod().equals("CANCEL")) {
                ssc.initResponse(OK_STATUS);
                ssc.send();
                origSSC.initResponse(REQUEST_TERMINATED_STATUS);
                origSSC.send();
                state = S_OFFLINE;
                str = new StringItem(SESSION_CANCEL, 
                   ssc.getHeader(CALL_ID_HEADER));
                form.append(str);
                form.removeCommand(byeCmd);
                form.removeCommand(answerCmd);
                form.addCommand(restartCmd);
             }else{}
          } catch(IOException iox) {
             iox.printStackTrace();
             form.append("IO"+EXCEPTION + iox.getMessage());
          } catch(Exception ex) {
             ex.printStackTrace();
             form.append("Other"+EXCEPTION + ex.getMessage());
          }
       }
    
    
  8. Create a method for ending the session. The functionality is basically the same as in the previous OriginatingInvite MIDlet. For more information, see the relevant section of this tutorial.

       private void sendBYE() {
          if(dialog != null) {
             try {
                SipClientConnection sc = 
                   dialog.getNewClientConnection(BYE_METHOD);
                sc.setHeader(ACCEPT_CONTACT_HEADER,
                   "*;type=\"" + SDP_MIME_TYPE + "\"");
                sc.send();
                str = new StringItem(HANG_UP, BYE_METHOD + " sent...");
                form.append(str);
    
                boolean gotit = sc.receive(RECEIVE_TIMEOUT);
                if(gotit) {
                   if(sc.getStatusCode() == OK_STATUS) {
                      form.append(SESSION_CLOSE_NOTIFY);
                      state = S_OFFLINE;
                   }
                   else
                      form.append("Error: "+sc.getReasonPhrase());
                }
                form.append("Dialog state: " + dialog.getState());
                sc.close();
             } catch(IOException iox) {
                form.append("Exception: "+iox.getMessage());
             }
          } else {
             form.append("No dialog information!");
          }
       }
    
    
  9. Include these two methods for pausing and terminating the MIDlet.

       /**
        * Signals the MIDlet to enter the Paused state.
        */
       public void pauseApp() {
          // pause
       }
    
       /**
        * Signals the MIDlet to terminate and enter the Destroyed state.
        * @param b boolean If true when this method is called, the MIDlet
        * must cleanup and release all resources. If false the MIDlet may
        * throw MIDletStateChangeException to indicate it does not want to be
        * destroyed at this time.
        */
       public void destroyApp(boolean b) {
          notifyDestroyed();
       }
    
    }