Visão geral do protocolo de início de sessão

O Android oferece uma API compatível com o Protocolo de início de sessão (SIP, na sigla em inglês). Isso permite que você adicione recursos de telefonia via Internet baseados em SIP aos seus aplicativos. O Android inclui uma pilha completa do protocolo SIP e serviços integrados de gerenciamento de chamadas que permitem que os aplicativos configurem facilmente chamadas de voz recebidas e efetuadas, sem precisar gerenciar sessões, comunicação no nível de transporte, gravação ou reprodução de áudio diretamente.

Veja alguns exemplos dos tipos de aplicativos que podem usar a API SIP:

  • Videoconferência
  • Mensagem instantânea

Requisitos e limitações

Veja os requisitos para desenvolver um aplicativo com SIP:

  • Você precisa ter um dispositivo móvel com o Android 2.3 ou versão posterior.
  • O SIP é executado em uma conexão de dados sem fio. Por isso, o dispositivo precisa ter uma conexão de dados (com um serviço móvel de dados ou Wi-Fi). Isso significa que não é possível testar no AVD. Só é possível testar em um dispositivo físico. Para ver detalhes, consulte Testar aplicativos com SIP.
  • Cada participante da sessão de comunicação do aplicativo precisa ter uma conta SIP. Há muitos provedores SIP diferentes que oferecem contas SIP.

Classes e interfaces da API SIP

Veja um resumo das classes e uma interface (SipRegistrationListener) que estão incluídas na API SIP do Android:

Classe/interface Descrição
SipAudioCall Processa uma chamada de áudio de Internet por meio do SIP.
SipAudioCall.Listener Listener para eventos relacionados a uma chamada SIP, como quando uma chamada é recebida ("tocando") ou realizada ("chamando").
SipErrorCode Define os códigos de erro retornados durante ações SIP.
SipManager Oferece APIs para tarefas SIP, como iniciar conexões SIP, e fornece acesso a serviços SIP relacionados.
SipProfile Define um perfil SIP, incluindo uma conta SIP e informações de domínio e servidor.
SipProfile.Builder Classe auxiliar para a criação de um SipProfile.
SipSession Representa uma sessão SIP associada a uma caixa de diálogo do SIP ou uma transação independente que não está em uma caixa de diálogo.
SipSession.Listener Listener para eventos relacionados a uma sessão SIP, como quando uma sessão é registrada ("registrando") ou quando uma chamada é realizada ("chamando").
SipSession.State Define os estados de sessões SIP, como "registrando", "chamada realizada" e "em chamada".
SipRegistrationListener Uma interface que é um listener para eventos de registro SIP.

Criar um manifesto

Se você está desenvolvendo um aplicativo que usa a API SIP, lembre-se de que esse recurso só é compatível com o Android 2.3 (API nível 9) e versões posteriores da plataforma. Além disso, nem todos os dispositivos com o Android 2.3 (API nível 9) ou posterior oferecem compatibilidade com SIP.

Para usar o SIP, adicione as seguintes permissões ao manifesto do seu aplicativo:

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

Para garantir que seu aplicativo só possa ser instalado em dispositivos compatíveis com o SIP, adicione o seguinte ao manifesto do aplicativo:

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

Isso indica que o aplicativo requer o Android 2.3 ou posterior. Para mais informações, consulte Níveis de APIs e a documentação do elemento <uses-sdk>.

Para controlar a forma como seu aplicativo é filtrado de dispositivos que não são compatíveis com SIP (no Google Play, por exemplo), adicione o seguinte ao manifesto do seu aplicativo:

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

Isso declara que seu aplicativo usa a API SIP. Essa declaração deve incluir um atributo android:required que indica se você quer que o aplicativo seja filtrado de dispositivos que não oferecem compatibilidade com SIP. Outras declarações <uses-feature> também podem ser necessárias, dependendo da implementação. Para mais informações, consulte a documentação do elemento <uses-feature>.

Se o aplicativo for projetado para receber chamadas, também será necessário definir um destinatário (subclasse BroadcastReceiver) no manifesto do aplicativo:

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

Veja trechos do manifesto 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>
    

Criar o SipManager

Para usar a API SIP, seu aplicativo precisa criar um objeto SipManager. O SipManager é responsável pelas seguintes ações no seu aplicativo:

  • Iniciar sessões SIP.
  • Iniciar e receber chamadas.
  • Registrar e cancelar o registro com um provedor SIP.
  • Verificar a conectividade da sessão.

Instancie um novo SipManager da seguinte forma:

Kotlin

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

Java

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

Como registrar-se com um servidor SIP

Um aplicativo comum com SIP para Android envolve um ou mais usuários, cada um com uma conta SIP. Em um aplicativo com SIP para Android, cada conta SIP é representada por um objeto SipProfile.

Um SipProfile define um perfil SIP, incluindo uma conta SIP e informações de domínio e servidor. O perfil associado à conta SIP no dispositivo que executa o aplicativo é denominado perfil local. O perfil ao qual a sessão é conectada é denominado perfil semelhante. Quando seu aplicativo SIP faz login no servidor SIP com o SipProfile local, isso efetivamente registra o dispositivo como o local para onde enviar chamadas SIP para seu endereço SIP.

Esta seção mostra como criar um SipProfile, registrá-lo com um servidor SIP e rastrear eventos de registro.

Crie um objeto SipProfile da seguinte forma:

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();
    

O trecho de código a seguir abre o perfil local para realizar chamadas e/ou receber chamadas SIP genéricas. O autor da chamada pode realizar chamadas subsequentes por meio do mSipManager.makeAudioCall. Esse trecho também define a ação android.SipDemo.INCOMING_CALL, que será usada por um filtro de intent quando o dispositivo receber uma chamada. Consulte Configurar um filtro de intent para receber chamadas. Esta é a etapa 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);
    

Por fim, este código define um SipRegistrationListener no SipManager. Isso rastreia se o SipProfile foi registrado no seu provedor de serviços 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.");
        }
    }
    

Quando seu aplicativo terminar de usar um perfil, ele deverá fechá-lo para liberar objetos associados na memória e cancelar o registro do dispositivo no servidor. Por exemplo:

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);
         }
    }
    

Fazer uma chamada de áudio

Para fazer uma chamada de áudio, é preciso ter o seguinte:

  • Um SipProfile que faça a chamada (o “perfil local”) e um endereço SIP válido para receber a chamada (o “perfil semelhante”).
  • Um objeto SipManager.

Para fazer uma chamada de áudio, configure um SipAudioCall.Listener. Grande parte da interação do cliente com a pilha do SIP acontece por meio dos listeners. Neste snippet, é possível ver como o SipAudioCall.Listener faz as configurações depois que a chamada é estabelecida:

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.
       }
    };
    

Depois de configurar o SipAudioCall.Listener, você pode fazer a chamada. O método SipManager makeAudioCall utiliza os seguintes parâmetros:

  • Um perfil SIP local (o autor da chamada).
  • Um perfil SIP semelhante (o usuário que recebe a chamada).
  • Um SipAudioCall.Listener para ouvir os eventos de chamada de SipAudioCall. Esse parâmetro pode ser null, mas, como mostrado acima, o listener é usado para definir as configurações quando a chamada é estabelecida.
  • O valor de tempo limite, em segundos.

Por exemplo:

Kotlin

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

Java

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

Receber chamadas

Para receber chamadas, um aplicativo SIP precisa incluir uma subclasse de BroadcastReceiver que possa responder a um intent indicando que há uma chamada recebida. Portanto, é preciso fazer o seguinte no seu aplicativo:

  • Em AndroidManifest.xml, declare um <receiver>. Em SipDemo, isso é <receiver android:name=".IncomingCallReceiver" android:label="Call Receiver" />.
  • Implemente o destinatário, que é uma subclasse de BroadcastReceiver. Em SipDemo, isso é IncomingCallReceiver.
  • Inicialize o perfil local (SipProfile) com um intent pendente que dispara o destinatário quando alguém faz uma chamada para o perfil local.
  • Configure um filtro de intent que filtre pela ação que representa uma chamada recebida. Em SipDemo, essa ação é android.SipDemo.INCOMING_CALL.

Criar uma subclasse de BroadcastReceiver

Para receber chamadas, seu aplicativo SIP precisa ter a subclasse BroadcastReceiver. O sistema Android processa chamadas SIP recebidas e transmite um intent de "chamada recebida", conforme definido pelo aplicativo, quando recebe uma chamada. Veja o código BroadcastReceiver com subclasses da amostra 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();
                }
            }
        }
    }
    

Como configurar um filtro de intent para receber chamadas

Quando o serviço SIP recebe uma nova chamada, ele envia um intent com a string de ação fornecida pelo aplicativo. No SipDemo, essa string de ação é android.SipDemo.INCOMING_CALL.

Esse trecho de código do SipDemo mostra como o objeto SipProfile é criado com um intent pendente baseado na string de ação android.SipDemo.INCOMING_CALL. O objeto PendingIntent realizará uma transmissão quando o SipProfile receber uma chamada:

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

A transmissão será interceptada pelo filtro de intent, que disparará o destinatário (IncomingCallReceiver). Você pode especificar um filtro de intent no arquivo de manifesto do aplicativo ou fazer isso por meio do código, como no método onCreate() da amostra de aplicativo SipDemo da Activity do aplicativo:

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);
           ...
        }
        ...
    }
    

Testar aplicativos SIP

Para testar aplicativos SIP, você precisa do seguinte:

  • Um dispositivo móvel com o Android 2.3 ou versões posteriores. O SIP é executado por meio de conexão sem fio. Por isso, é necessário testar em um dispositivo real. Não é possível realizar os testes no AVD.
  • Uma conta SIP. Há muitos provedores SIP diferentes que oferecem contas SIP.
  • Se você fizer uma chamada, ela também precisará ser para uma conta SIP válida.

Para testar um aplicativo SIP:

  1. No dispositivo, conecte-se à rede sem fio (Config. > Redes sem fio e outras > Wi-Fi > Configurações de Wi-Fi).
  2. Configure seu dispositivo móvel para testes, conforme descrito em Executar apps em um dispositivo de hardware.
  3. Execute seu aplicativo no dispositivo móvel, conforme descrito em Executar apps em um dispositivo de hardware.
  4. Se você está usando o Android Studio, é possível ver o resultado do registro do aplicativo abrindo o console Event Log (View > Tool Windows > Event Log).
  5. Verifique se o aplicativo está configurado para iniciar o Logcat automaticamente quando ele for executado:
    1. Selecione Run > Edit Configurations.
    2. Selecione a guia Miscellaneous na janela Run/Debug Configurations.
    3. Em Logcat, selecione Show logcat automatically e, em seguida, OK.