Descripción general del protocolo de inicio de sesión

Android ofrece una API que admite el Protocolo de inicio de sesión (SIP). Esto te permite agregar funciones de telefonía por Internet basadas en SIP a tus aplicaciones. Android incluye una pila completa de protocolos SIP y servicios integrados de administración de llamadas que permiten que las aplicaciones configuren fácilmente llamadas de voz entrantes y salientes sin tener que administrar sesiones, comunicación a nivel de transporte o grabación o reproducción de audio directamente.

Estos son ejemplos de los tipos de aplicaciones que pueden usar la API de SIP:

  • Videoconferencias
  • Mensajería instantánea

Requisitos y limitaciones

Estos son los requisitos para desarrollar una aplicación SIP:

  • Debes tener un dispositivo móvil con Android 2.3 o versiones posteriores.
  • SIP se ejecuta a través de una conexión de datos inalámbrica, por lo que tu dispositivo debe tener una conexión de datos (con un servicio de datos móviles o Wi-Fi). Esto significa que no puedes realizar pruebas en AVD; solo puedes hacerlo en un dispositivo físico. Para obtener más información, consulta Cómo probar aplicaciones SIP.
  • Cada participante de la sesión de comunicación de la aplicación debe tener una cuenta SIP. Hay muchos proveedores de SIP diferentes que ofrecen cuentas SIP.

Clases e interfaces de API de SIP

Este es un resumen de las clases y una interfaz (SipRegistrationListener) que se incluyen en la API de SIP de Android:

Clase/interfaz Descripción
SipAudioCall Procesa una llamada de audio de Internet a través de SIP.
SipAudioCall.Listener Objetos de escucha de eventos relacionados con una llamada de SIP, como cuando se recibe una llamada ("sonando") o sale una llamada ("llamando").
SipErrorCode Define los códigos de error que se muestran durante las acciones de SIP.
SipManager Proporciona API para tareas de SIP, como iniciar conexiones SIP, y proporciona acceso a servicios SIP relacionados.
SipProfile Define un perfil de SIP, que incluye una cuenta SIP y la información de dominio y servidor.
SipProfile.Builder Clase auxiliar para crear un SipProfile.
SipSession Representa una sesión SIP asociada con un diálogo SIP o una transacción independiente que no está dentro de un diálogo.
SipSession.Listener Elemento de escucha para eventos relacionados con una sesión SIP, como cuando se registra una sesión ("registrando") o sale una llamada ("llamando").
SipSession.State Define estados de sesión SIP, como "registrando", "llamada saliente" y "en llamada".
SipRegistrationListener Una interfaz que es un objeto de escucha de los eventos de registro SIP.

Cómo se crea el manifiesto

Si estás desarrollando una aplicación que usa la API de SIP, recuerda que la función solo es compatible con Android 2.3 (API nivel 9) y versiones posteriores de la plataforma. Además, entre los dispositivos con Android 2.3 (API nivel 9) o versiones posteriores, no todos los dispositivos ofrecerán asistencia para SIP.

Para usar SIP, agrega los siguientes permisos al manifiesto de tu aplicación:

  • android.permission.USE_SIP
  • android.permission.INTERNET

Para asegurarte de que tu aplicación solo se pueda instalar en dispositivos compatibles con SIP, agrega lo siguiente a su manifiesto:

<uses-sdk android:minSdkVersion="9" />

Esto indica que tu aplicación requiere Android 2.3 o versiones posteriores. Para obtener más información, consulta Niveles de API y la documentación del elemento <uses-sdk>.

Para controlar cómo se filtra tu aplicación desde dispositivos que no admiten SIP (por ejemplo, en Google Play), agrega lo siguiente a su manifiesto:

<uses-feature android:name="android.hardware.sip.voip" />

Esto declara que tu aplicación usa la API de SIP. La declaración debe incluir un atributo android:required que indique si quieres que la aplicación se filtre de dispositivos que no son compatibles con API Según tu implementación, es posible que también se necesiten otras declaraciones <uses-feature>. Para obtener más información, consulta la documentación relacionada con el elemento <uses-feature>.

Si tu aplicación está diseñada para recibir llamadas, también debes definir un receptor (subclase BroadcastReceiver) en su manifiesto:

<receiver android:name=".IncomingCallReceiver" android:label="Call Receiver" />

Estos son extractos del manifiesto de SipDemo:

<?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
              package="com.example.android.sip">
      ...
         <receiver android:name=".IncomingCallReceiver" android:label="Call Receiver" />
      ...
      <uses-sdk android:minSdkVersion="9" />
      <uses-permission android:name="android.permission.USE_SIP" />
      <uses-permission android:name="android.permission.INTERNET" />
      ...
      <uses-feature android:name="android.hardware.sip.voip" android:required="true" />
      <uses-feature android:name="android.hardware.wifi" android:required="true" />
      <uses-feature android:name="android.hardware.microphone" android:required="true" />
    </manifest>
    

Cómo crear SipManager

Para usar la API de SIP, tu aplicación debe crear un objeto SipManager. SipManager se encarga de lo siguiente en tu aplicación:

  • Iniciar sesiones SIP
  • Iniciar y recibir llamadas
  • Registrar y cancelar el registro con un proveedor SIP
  • Verificar la conectividad de la sesión

Crea una instancia nueva de SipManager de la siguiente manera:

Kotlin

    val sipManager: SipManager? by lazy(LazyThreadSafetyMode.NONE) {
        SipManager.newInstance(this)
    }
    

Java

    public SipManager sipManager = null;
    ...
    if (sipManager == null) {
        sipManager = SipManager.newInstance(this);
    }
    

Cómo registrarse con un servidor SIP

Una típica aplicación SIP para Android implica uno o más usuarios, cada uno de los cuales tiene una cuenta SIP. En una aplicación SIP para Android, cada cuenta SIP está representada por un objeto SipProfile.

Un SipProfile define un perfil de SIP, que incluye una cuenta SIP y la información de dominio y servidor. El perfil asociado con la cuenta SIP en el dispositivo que ejecuta la aplicación se denomina perfil local. El perfil al que está conectada la sesión se llama perfil similar. Cuando tu aplicación SIP accede al servidor SIP con el SipProfile local, esto registra efectivamente el dispositivo como la ubicación para enviar llamadas SIP a tu dirección de SIP.

En esta sección, se muestra cómo crear un SipProfile, registrarlo en un servidor SIP y rastrear eventos de registro.

Crea un objeto SipProfile de la siguiente manera:

Kotlin

    private var sipProfile: SipProfile? = null
    ...

    val builder = SipProfile.Builder(username, domain)
            .setPassword(password)
    sipProfile = builder.build()
    

Java

    public SipProfile sipProfile = null;
    ...

    SipProfile.Builder builder = new SipProfile.Builder(username, domain);
    builder.setPassword(password);
    sipProfile = builder.build();
    

El siguiente extracto de código abre el perfil local para realizar o recibir llamadas SIP genéricas. El emisor puede hacer llamadas subsiguientes a través de mSipManager.makeAudioCall. Este extracto también configura la acción android.SipDemo.INCOMING_CALL, que un filtro de intents usará cuando el dispositivo reciba una llamada (consulta Cómo configurar un filtro de intents para recibir llamadas). Este es el paso de registro:

Kotlin

    val intent = Intent("android.SipDemo.INCOMING_CALL")
    val pendingIntent: PendingIntent = PendingIntent.getBroadcast(this, 0, intent, Intent.FILL_IN_DATA)
    sipManager?.open(sipProfile, pendingIntent, null)
    

Java

    Intent intent = new Intent();
    intent.setAction("android.SipDemo.INCOMING_CALL");
    PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, Intent.FILL_IN_DATA);
    sipManager.open(sipProfile, pendingIntent, null);
    

Finalmente, este código configura un SipRegistrationListener en el SipManager. Esto realiza un seguimiento de si el SipProfile se registró correctamente con tu proveedor de servicios SIP:

Kotlin

    sipManager?.setRegistrationListener(sipProfile?.uriString, object : SipRegistrationListener {

        override fun onRegistering(localProfileUri: String) {
            updateStatus("Registering with SIP Server...")
        }

        override fun onRegistrationDone(localProfileUri: String, expiryTime: Long) {
            updateStatus("Ready")
        }

        override fun onRegistrationFailed(
                localProfileUri: String,
                errorCode: Int,
                errorMessage: String
        ) {
            updateStatus("Registration failed. Please check settings.")
        }
    })
    

Java

    sipManager.setRegistrationListener(sipProfile.getUriString(), new SipRegistrationListener() {

        public void onRegistering(String localProfileUri) {
            updateStatus("Registering with SIP Server...");
        }

        public void onRegistrationDone(String localProfileUri, long expiryTime) {
            updateStatus("Ready");
        }

        public void onRegistrationFailed(String localProfileUri, int errorCode,
            String errorMessage) {
            updateStatus("Registration failed.  Please check settings.");
        }
    }
    

Cuando tu aplicación termina de usar un perfil, debe cerrarlo para liberar objetos asociados en la memoria y cancelar el registro del dispositivo del servidor. Por ejemplo:

Kotlin

    fun closeLocalProfile() {
        try {
            sipManager?.close(sipProfile?.uriString)
        } catch (ee: Exception) {
            Log.d("WalkieTalkieActivity/onDestroy", "Failed to close local profile.", ee)
        }
    }
    

Java

    public void closeLocalProfile() {
        if (sipManager == null) {
           return;
        }
        try {
           if (sipProfile != null) {
              sipManager.close(sipProfile.getUriString());
           }
         } catch (Exception ee) {
           Log.d("WalkieTalkieActivity/onDestroy", "Failed to close local profile.", ee);
         }
    }
    

Cómo realizar una llamada de audio

Para realizar una llamada de audio, debes contar con lo siguiente:

  • Un SipProfile que realice la llamada (el "perfil local") y una dirección SIP válida para recibirla (el "perfil similar")
  • Un objeto SipManager

Para realizar una llamada de audio, debes configurar un SipAudioCall.Listener. Gran parte de la interacción del cliente con la pila SIP ocurre a través de los objetos de escucha. En este fragmento, puedes observar cómo SipAudioCall.Listener configura las cosas después de establecer la llamada:

Kotlin

    var listener: SipAudioCall.Listener = object : SipAudioCall.Listener() {

        override fun onCallEstablished(call: SipAudioCall) {
            call.apply {
                startAudio()
                setSpeakerMode(true)
                toggleMute()
            }
        }

        override fun onCallEnded(call: SipAudioCall) {
            // Do something.
        }
    }
    

Java

    SipAudioCall.Listener listener = new SipAudioCall.Listener() {

       @Override
       public void onCallEstablished(SipAudioCall call) {
          call.startAudio();
          call.setSpeakerMode(true);
          call.toggleMute();
             ...
       }

       @Override

       public void onCallEnded(SipAudioCall call) {
          // Do something.
       }
    };
    

Cuando hayas configurado SipAudioCall.Listener, podrás realizar la llamada. El makeAudioCall del método SipManager incluye los siguientes parámetros:

  • Un perfil de SIP local (el emisor).
  • Un perfil de SIP similar (el receptor de la llamada).
  • Un SipAudioCall.Listener para escuchar los eventos de llamada desde SipAudioCall. Esto puede ser null, pero, como se muestra arriba, el objeto de escucha se usa para configurar las cosas una vez que se establece la llamada.
  • El valor del tiempo de espera, en segundos.

Por ejemplo:

Kotlin

    val call: SipAudioCall? = sipManager?.makeAudioCall(
            sipProfile?.uriString,
            sipAddress,
            listener,
            30
    )
    

Java

    call = sipManager.makeAudioCall(sipProfile.getUriString(), sipAddress, listener, 30);
    

Cómo recibir llamadas

Para recibir llamadas, una aplicación SIP debe incluir una subclase de BroadcastReceiver que tenga la capacidad de responder a un intent que indique que hay una llamada entrante. Por lo tanto, debes hacer lo siguiente en tu aplicación:

  • En AndroidManifest.xml, declara un <receiver>. En SipDemo, esto es <receiver android:name=".IncomingCallReceiver" android:label="Call Receiver" />.
  • Implementa el receptor, que es una subclase de BroadcastReceiver. En SipDemo, esto es IncomingCallReceiver.
  • Inicializa el perfil local (SipProfile) con un intent pendiente que activa tu receptor cuando alguien llama al perfil local.
  • Configura un filtro de intents que filtre por la acción que representa una llamada entrante. En SipDemo, esta acción es android.SipDemo.INCOMING_CALL.

Cómo subclasificar BroadcastReceiver

Para recibir llamadas, tu aplicación SIP debe subclasificar BroadcastReceiver. El sistema Android procesa las llamadas SIP entrantes y transmite un intent de "llamada entrante" (según lo definido por la aplicación) cuando recibe una llamada. Aquí está el código BroadcastReceiver subclasificado del ejemplo de SipDemo.

Kotlin

    /**
     * Listens for incoming SIP calls, intercepts and hands them off to WalkieTalkieActivity.
     */
    class IncomingCallReceiver : BroadcastReceiver() {

        /**
         * Processes the incoming call, answers it, and hands it over to the
         * WalkieTalkieActivity.
         * @param context The context under which the receiver is running.
         * @param intent The intent being received.
         */
        override fun onReceive(context: Context, intent: Intent) {
            val wtActivity = context as WalkieTalkieActivity

            var incomingCall: SipAudioCall? = null
            try {
                incomingCall = wtActivity.sipManager?.takeAudioCall(intent, listener)
                incomingCall?.apply {
                    answerCall(30)
                    startAudio()
                    setSpeakerMode(true)
                    if (isMuted) {
                        toggleMute()
                    }
                    wtActivity.call = this
                    wtActivity.updateStatus(this)
                }
            } catch (e: Exception) {
                incomingCall?.close()
            }
        }

        private val listener = object : SipAudioCall.Listener() {

            override fun onRinging(call: SipAudioCall, caller: SipProfile) {
                try {
                    call.answerCall(30)
                } catch (e: Exception) {
                    e.printStackTrace()
                }
            }
        }
    }
    

Java

    /**
     * Listens for incoming SIP calls, intercepts and hands them off to WalkieTalkieActivity.
     */
    public class IncomingCallReceiver extends BroadcastReceiver {
        /**
         * Processes the incoming call, answers it, and hands it over to the
         * WalkieTalkieActivity.
         * @param context The context under which the receiver is running.
         * @param intent The intent being received.
         */
        @Override
        public void onReceive(Context context, Intent intent) {
            SipAudioCall incomingCall = null;
            try {
                SipAudioCall.Listener listener = new SipAudioCall.Listener() {
                    @Override
                    public void onRinging(SipAudioCall call, SipProfile caller) {
                        try {
                            call.answerCall(30);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                };
                WalkieTalkieActivity wtActivity = (WalkieTalkieActivity) context;
                incomingCall = wtActivity.sipManager.takeAudioCall(intent, listener);
                incomingCall.answerCall(30);
                incomingCall.startAudio();
                incomingCall.setSpeakerMode(true);
                if(incomingCall.isMuted()) {
                    incomingCall.toggleMute();
                }
                wtActivity.call = incomingCall;
                wtActivity.updateStatus(incomingCall);
            } catch (Exception e) {
                if (incomingCall != null) {
                    incomingCall.close();
                }
            }
        }
    }
    

Cómo configurar un filtro de intents para recibir llamadas

Cuando el servicio SIP recibe una nueva llamada, envía un intent con la string de acción que proporciona la aplicación. En SipDemo, esta string de acción es android.SipDemo.INCOMING_CALL.

Este extracto de código de SipDemo muestra cómo se crea el objeto SipProfile con un intent pendiente según la string de acción android.SipDemo.INCOMING_CALL. El objeto PendingIntent realizará una transmisión cuando SipProfile reciba una llamada:

Kotlin

    val sipManager: SipManager? by lazy(LazyThreadSafetyMode.NONE) {
        SipManager.newInstance(this)
    }

    var sipProfile: SipProfile? = null
    ...

    val intent = Intent("android.SipDemo.INCOMING_CALL")
    val pendingIntent: PendingIntent = PendingIntent.getBroadcast(this, 0, intent, Intent.FILL_IN_DATA)
    sipManager?.open (sipProfile, pendingIntent, null)
    

Java

    public SipManager sipManager = null;
    public SipProfile sipProfile = null;
    ...

    Intent intent = new Intent();
    intent.setAction("android.SipDemo.INCOMING_CALL");
    PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, Intent.FILL_IN_DATA);
    sipManager.open(sipProfile, pendingIntent, null);
    

La transmisión será interceptada por el filtro de intents, que luego activará el receptor (IncomingCallReceiver). Puedes especificar un filtro de intents en el archivo de manifiesto de tu aplicación, o hacerlo en código como en el método onCreate() de la aplicación de muestra SipDemo de Activity de la aplicación:

Kotlin

    class WalkieTalkieActivity : Activity(), View.OnTouchListener {
        ...
        lateinit var callReceiver: IncomingCallReceiver
        ...

        override fun onCreate(savedInstanceState: Bundle) {
            val filter = IntentFilter().apply {
                addAction("android.SipDemo.INCOMING_CALL")
            }
            callReceiver = IncomingCallReceiver()
            this.registerReceiver(callReceiver, filter)
            ...
        }
        ...
    }
    

Java

    public class WalkieTalkieActivity extends Activity implements View.OnTouchListener {
    ...
        public IncomingCallReceiver callReceiver;
        ...

        @Override
        public void onCreate(Bundle savedInstanceState) {

           IntentFilter filter = new IntentFilter();
           filter.addAction("android.SipDemo.INCOMING_CALL");
           callReceiver = new IncomingCallReceiver();
           this.registerReceiver(callReceiver, filter);
           ...
        }
        ...
    }
    

Cómo probar aplicaciones SIP

Para probar aplicaciones SIP, necesitas lo siguiente:

  • Un dispositivo móvil con Android 2.3 o versiones posteriores. SIP se ejecuta de forma inalámbrica, por lo que debes realizar una prueba en un dispositivo real. Las pruebas en AVD no funcionarán.
  • Una cuenta SIP. Hay muchos proveedores de SIP diferentes que ofrecen cuentas SIP.
  • Si realizas una llamada, también debe ser a una cuenta SIP válida.

Para probar una aplicación SIP:

  1. En tu dispositivo, conéctate a la conexión inalámbrica (Configuración > Conexiones inalámbricas y redes > Wi-Fi> Configuración de Wi-Fi).
  2. Configura tu dispositivo móvil para realizar pruebas, como se describe en Cómo desarrollar en un dispositivo.
  3. Ejecuta la aplicación en tu dispositivo móvil, como se describe en Cómo desarrollar en un dispositivo.
  4. Si usas Android Studio, puedes ver la salida del registro de la aplicación abriendo la consola de Registro de acontecimientos (View > Tool Windows > Event Log).
  5. Asegúrate de que tu aplicación esté configurada para iniciar Logcat automáticamente cuando se ejecute:
    1. Selecciona Run > Edit Configurations.
    2. Selecciona la pestaña Miscellaneous en la ventana Run/Debug Configurations.
    3. En Logcat, selecciona Show logcat automatically y, luego, selecciona OK.