Implementing the OriginatingInvite MIDlet

The OriginatingInvite MIDlet creates a form for printing progress information. The form contains a field for the address and port for the host where the TerminatingInvite MIDlet is running. The MIDlet also sets up the following commands: Call... and Exit.

If the command handler for the MIDlet handles the command Call... the following occurs:

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

For more information, see 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 OriginatingInvite 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.*;
    
    
  3. Create the variables needed for the connection.

    /**
     * OriginatingInvite: Implements a SIP messaging session initiator via JSR-180.
     * It establishes SIP session by sending "INVITE - ACK", and 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 OriginatingInvite extends MIDlet implements CommandListener, 
       SipClientConnectionListener, SipServerConnectionListener {
    
       private Display display;
       private long startTime;
       private Form form;
       private TextField address;
       private Command startCmd;
       private Command restartCmd;
       private Command byeCmd;
       private Command exitCmd;
       private SipDialog dialog;
       private StringItem str;
    
    

    The variables below will be used later in the code by the CommandAction method to recognize the action it needs to take.

       //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;
    
    

    The attributes below define the SIP methods, various descriptive texts used to convey the progress of the session, the post information of both this MIDlet and the receiving party and SipClientConnection's header information. For more information, see SipClientConnection in the JSR-180 specification.

       //Protocol constants - headers:
       private final String FROM_HEADER = "From";
       private final String TO_HEADER="To";
       private final String CONTACT_HEADER = "Contact";
       private final String CONTENT_LENGTH_HEADER = "Content-Length";
       private final String CONTENT_TYPE_HEADER = "Content-Type";
       private final String ACCEPT_CONTACT_HEADER = "Accept-Contact";
       private final String CALL_ID_HEADER = "Call-ID";
    
       //Protocol constants - some header values
       private final String SDP_MIME_TYPE = "application/sdp";
       private final String ACCEPT_CONTACT = "*;type=\"" + 
          SDP_MIME_TYPE + "\"";
       private final String DESTINATION_SIP_URI = "sip:sip2@host:5060";
    
       //Protocol constants - status
       private final int OK_STATUS = 200;
       private final int RING_STATUS = 180;
       private final int UNSUCCESS_STATUS = 400;
       private final int METHOD_NOT_ALLOWED_STATUS = 405;
    
       //Protocol constants - methods
       private final String INVITE_METHOD = "INVITE";
       private final String PRACK_METHOD = "PRACK";
       private final String BYE_METHOD = "BYE";
    
       //Timeouts values
       private final int TIME_OUT = 30000;
       private final int RECEIVE_TIMEOUT = 15000;
    
       //UI labels and messages
       private final String INVITE_LABEL = "INVITE";
       private final String BYE_COMMAND_LABEL = "Hang-up";
       private final String FORM_LABEL = "Session example";
       private final String RESTART_CMD_LABEL = "Restart";
       private final String START_CMD_LABEL = "Call...";
       private final String EXIT_CMD_LABEL = "Exit";
       private final String SCC_RES = "Response: ";
       private final String RING = "RINGING...\n";
       private final String DIALOG_LABEL = "Early-Dialog state: ";
       private final String DIALOG_STATE = "Dialog state: ";
       private final String SESSION_ESTABLISHED = "Session established: ";
       private final String SESSION_FAIL = "Session failed: ";
       private final String NO_ANSWER = "No answer: ";
       private final String HANG_UP = "user hang-up: ";
       private final String SESSION_CLOSE_NOTIFY = 
          "Session closed successfully...";
       private final String ERROR = "Error: ";
       private final String EXCEPTION = "Exception: ";
       private final String NO_DIALOG_INFO = "No dialog information!";
       private final String SESSION_CANCEL = "Session canceled: ";
       private final String ERROR_CANCEL = "Error canceling the call...";
       private final String OTHER_HANG_UP = "Other side hang-up!";
       private final String CLOSE_NOTIFIER = "Closing notifier...";
    
    

    Create the basic definitions for the scn, scc and ssc objects.

       private short state = S_OFFLINE;
       private SipConnectionNotifier scn;
       private SipServerConnection ssc = null;
       private SipClientConnection scc = null;
       private String contact = null;
    
    
  4. Create a String to provide SDP content for this example. It will be used later in the code to construct the OutputStream. For more information about the SDP, see the SIP API Overview section or the IETF RFC 2327 memo.

       // using static SDP content 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";
    
  5. Create a new OriginatingInvite object as shown below. Make sure it includes the initializing of the MIDlet display and creates a Form object to hold the progress info printings. Also, be sure to add the commands that will be used to control the operations here.

       /**
        * Creates a new originatingInvite object.
        */
       public OriginatingInvite() {
          // Initialize MIDlet display
          display=Display.getDisplay(this);
          // create a Form for progess info printings
          form = new Form(FORM_LABEL);
          address = new TextField(INVITE_LABEL + ": ", DESTINATION_SIP_URI, 
             40, TextField.LAYOUT_LEFT);
          form.append(address);
          byeCmd = new Command(BYE_COMMAND_LABEL, Command.CANCEL, 1);
          restartCmd = new Command(RESTART_CMD_LABEL, Command.OK, 1);
          startCmd = new Command(START_CMD_LABEL, Command.OK, 1);
          form.addCommand(startCmd);
          exitCmd = new Command(EXIT_CMD_LABEL, Command.EXIT, 1);
          form.addCommand(exitCmd);
          form.setCommandListener(this);
       }
    
    
  6. Create a commandAction object to give functionality to the Commands created in step 5. startCmd clears the form, enters the S_CALLING state and calls the startSession method, which is detailed later in this code in Step 8.

       /**
        * 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 excuting the command
        */
       public void commandAction(Command c, Displayable d) {
          if(c == startCmd) {
             form.deleteAll();
             form.removeCommand(startCmd);
             form.addCommand(byeCmd);
    
             state = S_CALLING;
             Thread t = new Thread() {
                public void run() {
                   startSession();
                }
             };
             t.start();
             return;
          }
          else if(c == exitCmd) {
             destroyApp(true);
             return;
          }
    

    Create byeCmd that either cancels the attempt or terminates the contact using the sendBYE or sendCancel methods described later in the code in Steps 10 and 11.

          else if(c == byeCmd) {
             if(state == S_RINGING) {
                sendCANCEL();
             } else {
                sendBYE();
             }
             form.removeCommand(byeCmd);
             form.addCommand(restartCmd);
             return;
          }
          else if(c == restartCmd) {
             stopListener();
             form.removeCommand(restartCmd);
             form.addCommand(startCmd);
             form.deleteAll();
             form.append(address);
             return;
          }
       }
    
  7. Create 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);
       }
    
    
  8. Write code, such as shown below, that can be used to start a new session between the MIDlets. It contains the necessary information needed make contact with the other party . The startListener method is described in Step 14.

       private void startSession() {
          try {
             state = S_CALLING;
             // start a listener for incoming requests
             startListener();
             form.append("Waiting for the connection.");
    

    Set the listener and initialize the INVITE. The setListener method (from SipClientConnection) is used to set up a listener for incoming responses. If one is already set, it will be overwritten. The INVITE is initialized with the initRequest method. For more information, see setListener in the JSR-180 specification. The initRequest method is described in the JSR-180 document. This method also has specific information regarding its use in the S60 implementation. See the SIP API for JavaTM 2 Micro Edition (JSR-180): Implementation Notes for further details.

    Use the setHeader method to set values to the headers used in this example. For more information, see the JSR-180 specification. setHeader and open methods also have specific information regarding their use in the S60 implementation. See the SIP API for JavaTM 2 Micro Edition (JSR-180): Implementation Notes for further details. For more information about headers in SIP, see RFC 3261. For S60 implementation specific additional information about headers, see the Additional notes section of the SIP API for JavaTM 2 Micro Edition (JSR-180): Implementation Notes.

             //  SIP connection with remote user
             scc = (SipClientConnection)
             Connector.open(address.getString());
             scc.setListener(this);
             // initialize INVITE request
             scc.initRequest(INVITE_METHOD, scn);
             scc.setHeader(ACCEPT_CONTACT_HEADER, ACCEPT_CONTACT);
             scc.setHeader(CONTACT_HEADER,contact);
             scc.setHeader(CONTENT_LENGTH_HEADER, ""+sdp.length());
             scc.setHeader(CONTENT_TYPE_HEADER, SDP_MIME_TYPE);
    

    The below code defines the OutputStream with the openContentOutputStream method, which is used to fill the SIP message body content. When calling close() on OutputStream, the message will be sent to the network. Finally, the INVITE_LABEL and TO_HEADER are added to the form by append method. For more information, see openContentOutputStream method in the JSR-180 specification. The getHeader method also has specific information regarding its use in the S60 implementation. See the SIP API for JavaTM 2 Micro Edition (JSR-180): Implementation Notes for further details.

             OutputStream os = scc.openContentOutputStream();
             os.write(sdp.getBytes());
             os.close(); // close and send
             str = new StringItem(INVITE_LABEL + 
                "...", scc.getHeader(TO_HEADER));
             form.append(str);
          } catch(Exception ex) {
             ex.printStackTrace();
             form.append(EXCEPTION + ex.getMessage());
          }
       }
    
    
  9. Write a method such as the notifyResponse method shown below, that is used to process incoming responses from the other party. The way the processing is done depends on the status of the response.

       /**
        * Processes incoming responses sent from the other party based on its
        * contents.
        * @param SipClientConnection the object represents a SIP client
        * transaction.
        */
       public void notifyResponse(SipClientConnection scc) {
          int statusCode = 0;
          boolean received = false;
    

    In the code below, the receive method is used for receiving SIP response messages. The method will update the SipClientConnection with the last new received response. The getStatusCode method gets the SIP response status code from the SipClientConnection object. getReasonPhrase fetches the SIP response reason phrase.

    In the below code, the getDialog and thegetState methods are used to add information to the form's DIALOG_LABEL variable. For more information, see receive, getStatusCode, getReasonPhrase, getDialog and getState in the JSR-180 specification.

          try {
             scc.receive(0); // fetch response
             statusCode = scc.getStatusCode();
             str = new StringItem(SCC_RES, 
                statusCode+" "+scc.getReasonPhrase());
             form.append(str);
             if(statusCode < OK_STATUS) {
                dialog = scc.getDialog();
                form.append(DIALOG_LABEL+dialog.getState()+"\n");
                if(statusCode == RING_STATUS){
                   state = S_RINGING;
                   form.append(RING);
                }
             }
    

    For the event that the fetched statusCode equals OK_STATUS, you can just insert a text notification that the SDP has been received, as the example doesn't go into handling the contents of the SDP.

             if(statusCode == OK_STATUS) {
                String contentType = scc.getHeader(CONTENT_TYPE_HEADER);
                String contentLength = scc.getHeader(CONTENT_LENGTH_HEADER);
                int length = Integer.parseInt(contentLength);
                if(contentType.equals(SDP_MIME_TYPE)) {
                   form.append("sdp content received.");
                }
    

    In the below code, the initAck method is used to initialize and send the ACK SIP method described in the chapter SIP request methods. For more information, see initAck in the JSR-180 specification. It's purpose is to establish the SIP session.

                form.append(DIALOG_STATE+dialog.getState());
                scc.initAck(); // initialize and send ACK
                scc.setHeader(ACCEPT_CONTACT_HEADER, ACCEPT_CONTACT);
                scc.send();
                dialog = scc.getDialog(); // save dialog info
                str = new StringItem(SESSION_ESTABLISHED, 
                   scc.getHeader(CALL_ID_HEADER));
                form.append(str);
                state = S_OFFLINE;
    

    If the statusCode is equal or greater than UNSUCCESS_STATUS, the scc closes and state changes to S_OFFLINE.

             }else if(statusCode >= UNSUCCESS_STATUS){
                str = new StringItem(SESSION_FAIL,
                scc.getHeader(CALL_ID_HEADER));
                form.append(str);
                form.removeCommand(byeCmd);
                form.addCommand(restartCmd);
                scc.close();
                state = S_OFFLINE;
    
             }
          } catch(IOException ioe) {
             // handle e.g. transaction timeout here
             str = new StringItem(NO_ANSWER, ioe.getMessage());
             form.append(str);
             form.removeCommand(byeCmd);
             form.addCommand(restartCmd);
          }catch(Exception e){
          }
       }
    
    
  10. Create a method for ending the session. If the dialog is not null, a new SipClientConnection sc is created with getNewClientConnection method, using BYE_METHOD as the value. It is then sent.

    The getNewClientConnection method also has specific information regarding its use in the S60 implementation. See the SIP API for JavaTM 2 Micro Edition (JSR-180): Implementation Notes for further details.

       // Ends session with "BYE".
       private void sendBYE() {
          if(dialog != null) {
             try {
                SipClientConnection sc = 
                   dialog.getNewClientConnection(BYE_METHOD);
                sc.setHeader(ACCEPT_CONTACT_HEADER, ACCEPT_CONTACT);
                sc.send();
                str = new StringItem(HANG_UP, BYE_METHOD + " sent...");
                form.append(str);
    

    The receive method receives the SIP response message, which will update the connection with the last new received response. For more information, see receive in the JSR-180 specification. After this, if the StatusCode is OK, the SESSION_CLOSE_NOTIFY and the DIALOG_STATE, combined with the byte that the getState method fetches from dialog, are added to form. Otherwise, the form is appended with the Constant ERROR and the reason phrase for it.

                boolean gotit = sc.receive(RECEIVE_TIMEOUT);
                if(gotit) {
                   if(sc.getStatusCode() == OK_STATUS) {
                      form.append(SESSION_CLOSE_NOTIFY);
                      form.append(DIALOG_STATE + dialog.getState());
                   }
                   else
                   form.append(ERROR + sc.getReasonPhrase());
                }
                   sc.close();
                   state = S_OFFLINE;
             } catch(IOException iox) {
                form.append(EXCEPTION + iox.getMessage());
             }
          } else {
             form.append(NO_DIALOG_INFO);
          }
       }
    
    
  11. Create a method that can be used to cancel the sending and receiving. The initCancel method is used to initialize SipClientConnection's CANCEL request method. The CANCEL request will be built according to the original INVITE request within this connection. Note that the CANCEL request SHOULD NOT be sent to cancel a request other than INVITE. For more information, see initCancel in the JSR-180 specification. This method also has specific information regarding its use in the S60 implementation. See the SIP API for JavaTM 2 Micro Edition (JSR-180): Implementation Notes for further details.

       private void sendCANCEL() {
          if(scc != null) {
             try {
                SipClientConnection cancel = scc.initCancel();
                cancel.send();
                if(cancel.receive(TIME_OUT)) {
                   str = new StringItem(SESSION_CANCEL, 
                      cancel.getReasonPhrase());
                   form.append(str);
                   state = S_OFFLINE;
                } else {
                   form.append(ERROR_CANCEL);
                }
             } catch(Exception ex) {
                ex.printStackTrace();
                form.append(EXCEPTION + ex.getMessage());
             }
          }
       }
    

  12. Create these standard Midlet operations for pausing and terminating the application.

       /**
        * 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();
       }
    
       /**
        * Terminates the application.
        */
       public void shutdown() {
          destroyApp(false);
       }
    
    
    
    
  13. To accept a new SipServerConnection and open it, call the acceptAndOpen method. If there are no messages in the queue, the method will block until a new request is received. Use the getMethod method to check what the returned SIP method is. If the fetched request method equals BYE, the initResponse method initializes SipServerConnection with a specific SIP response to the received request. The default headers and reason phrase will be initialized automatically. After this the SipServerConnection is in Initialized state. The response can be sent. For more information about acceptAndOpen,getMethod and initResponse, see the JSR-180 specification. The notifyRequest, send and initResponse methods also have specific information regarding their use in the S60 implementation. See the SIP API for JavaTM 2 Micro Edition (JSR-180): Implementation Notes for further details.

       /**
        * Processes the requests based on contents.
        * @param scn SipConnectionNotifier the SIP server connection notifier
        * object
        */
       public void notifyRequest(SipConnectionNotifier sn) {
          try {
             ssc = scn.acceptAndOpen(); // blocking
             if(ssc.getMethod().equals(BYE_METHOD)) {
                // respond 200 OK to BYE
                ssc.initResponse(OK_STATUS);
                ssc.send();
                str = new StringItem(OTHER_HANG_UP, "");
                form.append(str);
                form.append(CLOSE_NOTIFIER);
                form.removeCommand(byeCmd);
                form.addCommand(restartCmd);
                scn.close();
                state = S_OFFLINE;
    

    In the code below, if the getMethod returns PRACK_METHOD (which is the SIP request method PRACK), initialize the ssc with OK_STATUS, in any other case send the METHOD_NOT_ALLOWED_STATUS response. The PRACK request plays the same role as ACK, but is for provisional responses. For more information about the PRACK method, see the RFC 3262 specification.

             } else {
                if(ssc.getMethod().equals(PRACK_METHOD)) {
                   ssc.initResponse(OK_STATUS);
                   ssc.send();
                } else {
                   // 405 Method Not Allowed
                   ssc.initResponse(METHOD_NOT_ALLOWED_STATUS);
                   ssc.send();
                }
             }
          } catch(IOException ex) {
             form.append(EXCEPTION + ex.getMessage());
          }
       }
    
    
    
  14. Use these two methods to either start or stop the listener.

       private void startListener() {
          try {
             if(scn != null)
                scn.close();
                scn = (SipConnectionNotifier) Connector.open("sip:*;type=\"" 
                   + SDP_MIME_TYPE + '"');
                contact = new String("sip:sip1@" +
                   scn.getLocalAddress() +
                   ":" + scn.getLocalPort());
                scn.setListener(this);
          } catch(IOException ex) {
             form.append(EXCEPTION + ex.getMessage());
          }
       }
    
       private void stopListener() {
          try {
             if(scn != null)
                scn.close();
                scn = null;
          } catch(IOException ex) {
             form.append(EXCEPTION + ex.getMessage());
          }
       }
    }