gt:                         catch (JainSipException e) {
context: /*
   * TeleStax, Open Source Cloud Communications
   * Copyright 2011-2015, Telestax Inc and individual contributors
   * by the @authors tag.
   *
   * This program is free software: you can redistribute it and/or modify
   * under the terms of the GNU Affero General Public License as
   * published by the Free Software Foundation; either version 3 of
   * the License, or (at your option) any later version.
   *
   * This program 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 Affero General Public License for more details.
   *
   * You should have received a copy of the GNU Affero General Public License
   * along with this program.  If not, see <http://www.gnu.org/licenses/>
   *
   * For questions related to commercial use licensing, please contact sales@telestax.com.
   *
   */
  
  package org.restcomm.android.sdk.SignalingClient.JainSipClient;
  
  import android.gov.nist.javax.sip.ResponseEventExt;
  import android.javax.sip.Transaction;
  
  import org.restcomm.android.sdk.RCClient;
  import org.restcomm.android.sdk.RCDevice;
  import org.restcomm.android.sdk.RCDeviceListener;
  import org.restcomm.android.sdk.util.RCLogger;
  
  import java.util.Arrays;
  import java.util.HashMap;
  
  /**
   * JainSipJob represents the context of a signaling action until it is either finished or an error occurs. All signaling actions MUST be started as jobs, because
   * jobs keep the context that binds requests & responses together. An important note is that not all jobs need an FSM, but whether an FSM is started or not
   * for a job depends on JainSipJob.hasFsm()
   *
   * A JainSipJob mainly holds the following information:
   * - jobId: that uniquely identifies a job within the signaling facilities. This typically is a unix timestamp (including milis)
   *   for outgoing requests provided in the request by the App, and the SIP Call-Id for incoming requests
   * - transaction: the SIP Transaction object associated with the job at this point in time. Remember that a job might consist of multiple transactions, hence
   *   this field might be updated during the job's lifetime
   * - parameters: a HashMap holding an arbitrary number of parameters applicable to the job. These can originate at the App, or they can be updated during the course
   *   of job execution
   */
  class JainSipJob {
     public enum FsmStates {
        START_BIND_REGISTER,
        AUTH,
        NOTIFY,
        REGISTER,
        UNREGISTER,
        SHUTDOWN,
        AUTH_1,
        AUTH_2,
        UNBIND_BIND_REGISTER,
        BIND_REGISTER,
     }
  
     public enum FsmEvents {
        NONE,
        TIMEOUT,
        AUTH_REQUIRED,
        REGISTER_FAILURE,
        REGISTER_SUCCESS,
     }
  
     /**
      * Some signaling jobs (important: not all jobs have an FSM) are also associated with a state machine to be able to properly address invoking
      * the same functionalities in different job contexts without losing track and at the same time notifying the correct UI entities of job status.
      *
      * The idea here is that you initialize a job and based on its type (i.e. TYPE_OPEN, etc) we have a set of states (i.e. FsmStates) that this jobs typically needs
      * to go through. For example job of type TYPE_OPEN needs to start the signaling stack, bind to networking facilities, register, authorize and finally
      * notify the App. These steps are reflected in the states member variable and initialized when the job is constructed.
      *
      * Once the FSM is initialized we can then call JainSipFsm.process() that will do the actual FSM processing based on current state (i.e. states variable) and the event (i.e. FsmEvents)
      * passed by the caller (check JainSipFsm.process())
      *
      * It's important to avoid calling any methods within JainSipFsm.process() that inside them call JainSipFsm.process(), cause that would probably cause corruption
      * of the FSM state
      *
      * Some areas that need further improvement at some point:
      * - JainSipFsm.process() code has become spaghetti so we need to consider avoiding code duplication
      * - Right now we use JainSipJob.parameters to access and store context information, which can sometimes proves error prone (especially for reconfigure jobs where we
      *   stuff in it two separate sub parameters
      * - when some state needs to be skipped the logic is lousy
      */
     class JainSipFsm {
        FsmStates[] states;
        JainSipJob.Type type;
        JainSipClient jainSipClient;
        static final String TAG = "JainSipFsm";
        int index;
  
        JainSipFsm(JainSipJob.Type type, JainSipClient jainSipClient)
        {
           init(type, jainSipClient);
        }
  
        void init(JainSipJob.Type type, JainSipClient jainSipClient)
        {
           this.type = type;
           this.jainSipClient = jainSipClient;
           index = 0;
  
           if (type == JainSipJob.Type.TYPE_OPEN) {
              states = new FsmStates[] { FsmStates.START_BIND_REGISTER, FsmStates.AUTH, FsmStates.NOTIFY};
           }
           else if (type == Type.TYPE_REGISTER_REFRESH) {
              states = new FsmStates[] { FsmStates.REGISTER, FsmStates.AUTH, FsmStates.NOTIFY};
           }
           else if (type == Type.TYPE_CLOSE) {
              states = new FsmStates[] { FsmStates.UNREGISTER, FsmStates.AUTH, FsmStates.SHUTDOWN};
           }
           else if (type == Type.TYPE_RECONFIGURE) {
              states = new FsmStates[] { FsmStates.UNREGISTER, FsmStates.AUTH_1, FsmStates.REGISTER, FsmStates.AUTH_2, FsmStates.NOTIFY};
           }
           else if (type == Type.TYPE_RECONFIGURE_RELOAD_NETWORKING) {
              states = new FsmStates[] { FsmStates.UNREGISTER, FsmStates.AUTH_1, FsmStates.UNBIND_BIND_REGISTER, FsmStates.AUTH_2, FsmStates.NOTIFY};
           }
           else if (type == Type.TYPE_RELOAD_NETWORKING) {
              states = new FsmStates[] { FsmStates.UNBIND_BIND_REGISTER, FsmStates.AUTH, FsmStates.NOTIFY};
           }
           else if (type == Type.TYPE_START_NETWORKING) {
              states = new FsmStates[] { FsmStates.BIND_REGISTER, FsmStates.AUTH, FsmStates.NOTIFY};
           }
        }
  
        /**
         * This is either called to start the FSM or resume it
         * @param jobId Id for the current job
         * @param event The input event provided by the caller, to help FSM understand what kind of transition to do
         * @param arg Optional argument for the job
         * @param statusCode Optional status code we want to convey (to the UI thread typically)
         * @param statusText Optional the status text we want to convey (to the UI thread typically)
         */
        void process(String jobId, FsmEvents event, Object arg, RCClient.ErrorCodes statusCode, String statusText)
        {
           if (statusCode == null) {
              statusCode = RCClient.ErrorCodes.SUCCESS;
           }
  
           if (statusText == null) {
              statusText = RCClient.errorText(RCClient.ErrorCodes.SUCCESS);
           }
  
           if (states == null) {
              // if states is null, then it means that no FSM should be used at all
              return;
           }
  
           if (JainSipJob.this.jobId.equals(jobId)) {
              boolean loop;
              do {
                 loop = false;
                 if (index >= states.length) {
                    RCLogger.e(TAG, "process(): no more states to process");
                 }
                 if (type == Type.TYPE_OPEN) {
                    // no matter what state we are in if we get a timeout we need to just bail
                    if (event.equals(FsmEvents.TIMEOUT)) {
                       jainSipClient.listener.onClientOpenedReply(jobId, RCDeviceListener.RCConnectivityStatus.RCConnectivityStatusNone,
                             RCClient.ErrorCodes.ERROR_DEVICE_REGISTER_TIMEOUT,
                             RCClient.errorText(RCClient.ErrorCodes.ERROR_DEVICE_REGISTER_TIMEOUT));
                       jainSipJobManager.remove(jobId);
                       return;
                    }
                    if (states[index].equals(FsmStates.START_BIND_REGISTER)) {
                       try {
                          jainSipClient.jainSipClientStartStack();
  
                          if (!jainSipClient.jainSipNotificationManager.haveConnectivity()) {
                             jainSipClient.listener.onClientOpenedReply(jobId, JainSipNotificationManager.networkStatus2ConnectivityStatus(jainSipClient.jainSipNotificationManager.getNetworkStatus()),
                                   RCClient.ErrorCodes.ERROR_DEVICE_NO_CONNECTIVITY,
                                   RCClient.errorText(RCClient.ErrorCodes.ERROR_DEVICE_NO_CONNECTIVITY));
                             jainSipJobManager.remove(jobId);
                             return;
                          }
                          jainSipClient.jainSipClientBind(parameters);
  
                          if (parameters.containsKey(RCDevice.ParameterKeys.SIGNALING_DOMAIN) && !parameters.get(RCDevice.ParameterKeys.SIGNALING_DOMAIN).equals("")) {
                             // Domain has been provided do the registration
                             transaction = jainSipClient.jainSipClientRegister(JainSipJob.this, parameters);
                          }
                          else {
                             // No Domain there we are done here
                             jainSipClient.listener.onClientOpenedReply(jobId, JainSipNotificationManager.networkStatus2ConnectivityStatus(jainSipClient.jainSipNotificationManager.getNetworkStatus()),
                                   RCClient.ErrorCodes.SUCCESS, RCClient.errorText(RCClient.ErrorCodes.SUCCESS));
                             jainSipJobManager.remove(jobId);
                          }
                       }
                       catch (JainSipException e) {
                          e.printStackTrace();
                          jainSipClient.listener.onClientOpenedReply(jobId, RCDeviceListener.RCConnectivityStatus.RCConnectivityStatusNone, e.errorCode, e.errorText);
                          jainSipJobManager.remove(jobId);
                       }
                    }
                    else if (states[index].equals(FsmStates.AUTH)) {
                       // the auth step is optional hence we check if auth-required event was passed by the caller, if not we loop around to visit next state
                       if (event.equals(FsmEvents.AUTH_REQUIRED)) {
                          ResponseEventExt responseEventExt = (ResponseEventExt) arg;
                          try {
                             jainSipClient.jainSipAuthenticate(JainSipJob.this, parameters, responseEventExt);
                          }
                          catch (JainSipException e) {
                             e.printStackTrace();
                             jainSipClient.listener.onClientOpenedReply(jobId, RCDeviceListener.RCConnectivityStatus.RCConnectivityStatusNone, e.errorCode, e.errorText);
                             jainSipJobManager.remove(jobId);
                          }
                       }
                       else {
                          loop = true;
                       }
                    }
                    else if (states[index].equals(FsmStates.NOTIFY)) {
                       if (event.equals(FsmEvents.REGISTER_FAILURE)) {
                          jainSipClient.listener.onClientOpenedReply(jobId, RCDeviceListener.RCConnectivityStatus.RCConnectivityStatusNone, statusCode, statusText);
                       }
                       if (event.equals(FsmEvents.REGISTER_SUCCESS)) {
                          jainSipClient.listener.onClientOpenedReply(jobId, JainSipNotificationManager.networkStatus2ConnectivityStatus(jainSipClient.jainSipNotificationManager.getNetworkStatus()),
                                statusCode, statusText);
                       }
  
                       if (event.equals(FsmEvents.REGISTER_SUCCESS) || event.equals(FsmEvents.REGISTER_FAILURE)) {
                          jainSipJobManager.remove(jobId);
                       }
                    }
                 }
                 else if (type == Type.TYPE_REGISTER_REFRESH) {
                    // no matter what state we are in if we get a timeout we need to just bail
                    if (event.equals(FsmEvents.TIMEOUT)) {
                       jainSipClient.listener.onClientErrorReply(jobId, RCDeviceListener.RCConnectivityStatus.RCConnectivityStatusNone,
                             RCClient.ErrorCodes.ERROR_DEVICE_REGISTER_TIMEOUT,
                             RCClient.errorText(RCClient.ErrorCodes.ERROR_DEVICE_REGISTER_TIMEOUT));
                       jainSipJobManager.remove(jobId);
                       return;
                    }
  
                    if (states[index].equals(FsmStates.REGISTER)) {
                       try {
                          transaction = jainSipClient.jainSipClientRegister(JainSipJob.this, parameters);
                       }
                       catch (JainSipException e) {
                          e.printStackTrace();
                          jainSipClient.listener.onClientErrorReply(jobId, RCDeviceListener.RCConnectivityStatus.RCConnectivityStatusNone, e.errorCode, e.errorText);
                          jainSipJobManager.remove(jobId);
                       }
                    }
                    else if (states[index].equals(FsmStates.AUTH)) {
                       // the auth step is optional hence we check if auth-required event was passed by the caller, if not we loop around to visit next state
                       if (event.equals(FsmEvents.AUTH_REQUIRED)) {
                          ResponseEventExt responseEventExt = (ResponseEventExt) arg;
                          try {
                             jainSipClient.jainSipAuthenticate(JainSipJob.this, parameters, responseEventExt);
                          }
                          catch (JainSipException e) {
                             e.printStackTrace();
                             jainSipClient.listener.onClientErrorReply(jobId, RCDeviceListener.RCConnectivityStatus.RCConnectivityStatusNone, e.errorCode, e.errorText);
                             jainSipJobManager.remove(jobId);
                          }
                       }
                       else {
                          loop = true;
                       }
                    }
                    else if (states[index].equals(FsmStates.NOTIFY)) {
                       if (event.equals(FsmEvents.REGISTER_FAILURE)) {
                          jainSipClient.listener.onClientErrorReply(jobId, RCDeviceListener.RCConnectivityStatus.RCConnectivityStatusNone, statusCode, statusText);
                       }
                       if (event.equals(FsmEvents.REGISTER_SUCCESS) || event.equals(FsmEvents.REGISTER_FAILURE)) {
                          jainSipJobManager.remove(jobId);
                       }
                    }
                 }
                 else if (type == Type.TYPE_CLOSE) {
                    if (states[index].equals(FsmStates.UNREGISTER)) {
                       try {
                          transaction = jainSipClient.jainSipClientUnregister(parameters);
                          final String finalId = jobId;
  
                          // Schedule a check to see if we managed to close the signaling facilities. If not then we need to force closing.
                          // The reason we need that is for example if the unregister times out, which in SIP takes 32 seconds. This means
                          // that after the App is left the SIP stack will remain alive for 32 secs, which means that if user tries to re-open
                          // the stack it will fail.
                          Runnable runnable = new Runnable() {
                             @Override
                             public void run()
                             {
                                if (jainSipClient.jainSipJobManager.get(finalId) != null) {
                                   RCLogger.e(TAG, "process(): Unregister is taking too long. Forcing signaling facilities to stop");
                                   // failed to unregister; we need to unbind & stop stack or at next initialization we will fail
                                   try {
                                      // don't forget to terminate the transaction, or else the timeout will fire and will be useless
                                      transaction.terminate();
                                      jainSipClient.jainSipClientUnbind();
                                      jainSipClient.jainSipClientStopStack();
                                   }
                                   catch (Exception e) {
                                      // at this point we can't recover
                                      throw new RuntimeException("Failed to release signaling facilities", e);
                                   }
                                }
                             }
                          };
                          jainSipClient.signalingHandler.postDelayed(runnable, JainSipClient.FORCE_CLOSE_INTERVAL);
  
                       }
                       catch (JainSipException e) {
                          e.printStackTrace();
                          jainSipClient.listener.onClientClosedEvent(jobId, e.errorCode, e.errorText);
  
                          // failed to unregister; we need to unbind & stop stack or at next initialization we will fail
                          try {
                             jainSipClient.jainSipClientUnbind();
                             jainSipClient.jainSipClientStopStack();
                          }
                          catch (JainSipException inner) {
                             // at this point we can't recover
                             throw new RuntimeException("Failed to unbind signaling facilities", e);
                          }
                          jainSipJobManager.remove(jobId);
                       }
                    }
                    else if (states[index].equals(FsmStates.AUTH)) {
                       // the auth step is optional hence we check if auth-required event was passed by the caller, if not we loop around to visit next state
                       if (event.equals(FsmEvents.AUTH_REQUIRED)) {
                          ResponseEventExt responseEventExt = (ResponseEventExt) arg;
                          try {
                             jainSipClient.jainSipAuthenticate(JainSipJob.this, parameters, responseEventExt);
                          }
  