Omówienie protokołu inicjowania sesji

Wykrywanie kart eSIM i SIM

Wykrywanie kart

Urządzenia z Androidem z kartami SIM i kartami eSIM oraz w interfejsach API telefonii używają tych identyfikatorów, w tym [`TelephonyManager`](/reference/android/telephony/TelephonyManager) i [`SubscriptionManager`](/reference/android/telephony/SubscriptionManager): * Identyfikator subskrypcji: unikalny identyfikator subskrypcji mobilnej. * Indeks lub identyfikator przedziału logicznego: unikalny indeks odnoszący się do logicznego gniazda SIM. Identyfikatory przedziałów logicznych zaczynają się od 0 i rosną w zależności od liczby aktywnych przedziałów na urządzeniu. Na przykład urządzenie dual SIM ma zwykle gniazdo 0 i gniazdo 1. Jeśli urządzenie ma wiele przedziałów fizycznych, ale obsługuje tylko 1 aktywny przedział, będzie miał tylko identyfikator przedziału logicznego 0. * Indeks lub identyfikator gniazda fizycznego: unikalny indeks odnoszący się do fizycznego gniazda SIM. Identyfikatory przedziałów fizycznych zaczynają się od 0 i rosną w zależności od liczby przedziałów fizycznych na urządzeniu. Różni się ona od liczby przedziałów logicznych na urządzeniu, która odpowiada liczbie aktywnych przedziałów, z których może korzystać. Na przykład urządzenie przełączające się między trybem 2 SIM i 1 SIM może zawsze mieć 2 gniazda fizyczne, ale w trybie pojedynczej karty SIM będzie mieć tylko 1 gniazdo logiczne. * Identyfikator karty: unikalny identyfikator używany do identyfikacji karty UiccCard. ![Schemat wykorzystania identyfikatorów w przypadku z 2 przedziałami logicznymi i 3 przedziałami fizycznymi](/images/guide/topics/connectivity/tel-ids.png) Na powyższym schemacie: * Urządzenie ma 2 przedziały logiczne. * W gnieździe fizycznym 0 znajduje się fizyczna karta UICC z aktywnym profilem. * W miejscu fizycznym 2 jest interfejs eUICC z aktywnym profilem. * Gniazdo fizyczne 1 nie jest obecnie używane. ![Schemat sposobu użycia identyfikatorów w przypadku z 3 przedziałami logicznymi i 2 przedziałami fizycznymi](/images/guide/topics/connectivity/tel-ids-2.png) Na powyższym schemacie: * Urządzenie ma 3 przedziały logiczne. * W gnieździe fizycznym 0 znajduje się fizyczna karta UICC z aktywnym profilem. * W miejscu fizycznym 1 jest eUICC z 2 pobranymi profilami, z których oba są aktywne za pomocą MEP (wielokrotnego włączenia profilu).

Omówienie protokołu inicjowania sesji

Android udostępnia interfejs API, który obsługuje protokół SIP (Session Initiation Protocol). Dzięki temu możesz dodać do swoich aplikacji funkcje telefonii internetowej opartej na SIP. Android obejmuje pełny stos protokołów SIP i zintegrowane usługi zarządzania połączeniami, które pozwalają aplikacjom z łatwością konfigurować wychodzące i przychodzące połączenia głosowe bez konieczności bezpośredniego zarządzania sesjami, komunikacji na poziomie transportowym ani bezpośredniego nagrywania czy odtwarzania dźwięku.

Oto przykłady aplikacji, które mogą korzystać z interfejsu SIP API:

  • Wideokonferencje
  • Komunikator internetowy

Wymagania i ograniczenia

Oto wymagania dotyczące tworzenia aplikacji SIP:

  • Musisz mieć urządzenie mobilne z Androidem 2.3 lub nowszym.
  • SIP działa przez bezprzewodowe połączenie do transmisji danych, dlatego Twoje urządzenie musi mieć połączenie do transmisji danych (z mobilną usługą transmisji danych lub Wi-Fi). Oznacza to, że nie możesz przeprowadzić testu za pomocą AVD, lecz tylko na urządzeniu fizycznym. Więcej informacji znajdziesz w artykule o testowaniu aplikacji SIP.
  • Każdy uczestnik sesji komunikacyjnej w aplikacji musi mieć konto SIP. Jest wielu różnych dostawców usług SIP, którzy oferują konta SIP.

Uwaga: biblioteka android.net.sip nie obsługuje rozmów wideo. Jeśli chcesz wdrożyć połączenia VOIP za pomocą stosu SIP, takiego jak android.net.sip, zapoznaj się z jedną z wielu nowoczesnych alternatyw open source jako podstawy do wdrożenia połączeń VOIP. Możesz też wdrożyć interfejs API ConnectionService, aby zapewnić ścisłą integrację tych wywołań z aplikacją Telefon na urządzeniu.

Klasy i interfejsy SIP API

Oto podsumowanie klas i 1 interfejsu (SipRegistrationListener), które wchodzi w skład Android SIP API:

Klasa/interfejs Opis
SipAudioCall Obsługuje internetowe połączenie głosowe przez SIP.
SipAudioCall.Listener Nasłuchiwanie zdarzeń związanych z połączeniem SIP, na przykład podczas odbierania połączenia („gdy dzwoni”) lub tego, że jest ono wychodzące („podczas nawiązywania połączenia”).
SipErrorCode Definiuje kody błędów zwracane podczas działań SIP.
SipManager Udostępnia interfejsy API do zadań SIP, takich jak inicjowanie połączeń SIP, oraz dostęp do powiązanych usług SIP.
SipProfile Definiuje profil SIP, w tym informacje o koncie SIP, domenie i serwerze.
SipProfile.Builder Klasa pomocnicza do tworzenia profilu SipProfile.
SipSession Reprezentuje sesję SIP, która jest powiązana z oknem SIP lub samodzielną transakcją, która nie znajduje się w oknie.
SipSession.Listener Detektor zdarzeń związanych z sesją SIP, np. podczas rejestracji sesji lub podczas nawiązywania połączenia.
SipSession.State Definiuje stany sesji SIP, takie jak „rejestracja”, „połączenie wychodzące” i „podczas rozmowy”.
SipRegistrationListener Interfejs, który jest odbiornikiem zdarzeń rejestracji SIP.

Tworzę plik manifestu

Jeśli tworzysz aplikację, która korzysta z interfejsu SIP API, pamiętaj, że ta funkcja jest obsługiwana tylko na Androidzie 2.3 (poziom API 9) i nowszych wersjach platformy. Pamiętaj też, że nie wszystkie urządzenia z Androidem 2.3 (poziom interfejsu API 9) lub nowszym zapewniają obsługę SIP.

Aby używać SIP, dodaj do pliku manifestu aplikacji te uprawnienia:

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

Aby mieć pewność, że Twoją aplikację można zainstalować tylko na urządzeniach obsługujących protokół SIP, dodaj do jej pliku manifestu ten fragment:

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

Oznacza to, że Twoja aplikacja wymaga Androida 2.3 lub nowszego. Więcej informacji znajdziesz w sekcji Poziomy interfejsów API i w dokumentacji elementu <uses-sdk>.

Aby kontrolować sposób, w jaki aplikacja jest odfiltrowywana z urządzeń, które nie obsługują SIP (np. w Google Play), do pliku manifestu aplikacji dodaj ten fragment kodu:

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

Oznacza to, że Twoja aplikacja korzysta z interfejsu SIP API. Deklaracja powinna zawierać atrybut android:required wskazujący, czy aplikacja ma być odfiltrowywana z urządzeń, które nie oferują obsługi SIP. W zależności od implementacji mogą być potrzebne też inne deklaracje <uses-feature>. Więcej informacji znajdziesz w dokumentacji elementu <uses-feature>.

Jeśli Twoja aplikacja jest przeznaczona do odbierania połączeń, musisz też zdefiniować w jej pliku manifestu podklasę odbiorcy (BroadcastReceiver):

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

Oto fragmenty pliku manifestu 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.software.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>

Tworzę menedżera SipManagera

Aby korzystać z interfejsu SIP API, aplikacja musi utworzyć obiekt SipManager. SipManager dba o te kwestie w Twojej aplikacji:

  • Inicjowanie sesji SIP.
  • Inicjowanie i odbieranie połączeń.
  • Rejestrowanie i wyrejestrowanie u dostawcy SIP.
  • Weryfikuję połączenie sesji.

Tworzysz nową instancję SipManager w ten sposób:

Kotlin

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

Java

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

Rejestracja na serwerze SIP

Typowa aplikacja SIP na Androidzie obejmuje co najmniej jednego użytkownika, z którego każdy ma konto SIP. W aplikacji SIP na Androida każde konto SIP jest reprezentowane przez obiekt SipProfile.

SipProfile definiuje profil SIP, w tym konto SIP, oraz informacje o domenie i serwerze. Profil powiązany z kontem SIP na urządzeniu, na którym działa aplikacja, jest nazywany profilem lokalnym. Profil, z którym połączona jest sesja, jest nazywany profilem peera. Gdy aplikacja SIP loguje się na serwer SIP za pomocą lokalnego numeru SipProfile, rejestruje to urządzenie jako lokalizację, na którą mają być wysyłane połączenia SIP trunk pod Twój adres.

W tej sekcji dowiesz się, jak utworzyć SipProfile, zarejestrować go na serwerze SIP i śledzić zdarzenia rejestracji.

Obiekt SipProfile możesz utworzyć w ten sposób:

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

Poniższy fragment kodu otwiera profil lokalny, gdzie można nawiązywać połączenia lub odbierać ogólne połączenia SIP. Rozmówca może nawiązywać kolejne połączenia za pomocą funkcji mSipManager.makeAudioCall. Ten fragment ustawia też działanie android.SipDemo.INCOMING_CALL, które będzie używane przez filtr intencji, gdy urządzenie otrzyma połączenie (patrz Konfigurowanie filtra intencji na potrzeby odbierania połączeń). Etap rejestracji:

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

Na koniec ten kod ustawia SipRegistrationListener na platformie SipManager. Pozwala to śledzić, czy urządzenie SipProfile zostało zarejestrowane u dostawcy usług 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.");
    }
}

Gdy aplikacja zakończy korzystanie z profilu, powinna ją zamknąć, uwolnić powiązane z nią obiekty i wyrejestrować urządzenie z serwera. Przykład:

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

Zaczynam rozmowę głosową

Aby prowadzić rozmowę głosową, musisz mieć:

  • SipProfile, który nawiązuje połączenie („profil lokalny”), i prawidłowy adres SIP, który umożliwia odebranie połączenia („profil połączenia równorzędnego”).
  • Obiekt SipManager.

Aby prowadzić rozmowę głosową, musisz skonfigurować urządzenie SipAudioCall.Listener. Znaczna część interakcji klienta ze stosem SIP odbywa się przez detektory. W tym fragmencie widać, jak SipAudioCall.Listener konfiguruje się po nawiązaniu połączenia:

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

Po skonfigurowaniu urządzenia SipAudioCall.Listener możesz zacząć dzwonić. Metoda SipManager makeAudioCall przyjmuje te parametry:

  • Lokalny profil SIP (rozmówca).
  • Profil SIP peera (wywoływany użytkownik).
  • SipAudioCall.Listener do nasłuchiwania zdarzeń wywołania z SipAudioCall. Może to być null, ale jak pokazano powyżej, odbiornik służy do konfiguracji ustawień po nawiązaniu połączenia.
  • Wartość limitu czasu w sekundach.

Na przykład:

Kotlin

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

Java

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

Odbieranie połączeń

Aby można było odbierać połączenia, aplikacja SIP musi zawierać podklasę klasy BroadcastReceiver z możliwością odpowiadania na intencję wskazującą na połączenie przychodzące. Dlatego w aplikacji musisz wykonać te czynności:

  • W polu AndroidManifest.xml zadeklaruj <receiver>. W SipDemo jest to <receiver android:name=".IncomingCallReceiver" android:label="Call Receiver" />.
  • Zaimplementuj odbiornik, który jest podklasą klasy BroadcastReceiver. W SipDemo jest to IncomingCallReceiver.
  • Zainicjuj profil lokalny (SipProfile) z intencją oczekiwania, która uruchamia odbiornik, gdy ktoś zadzwoni na profil lokalny.
  • Skonfiguruj filtr intencji, który będzie filtrować według działania reprezentującego połączenie przychodzące. W SipDemo to działanie jest android.SipDemo.INCOMING_CALL.

Podklasyfikacja BroadcastReceivedr

Aby można było odbierać połączenia, aplikacja SIP musi należeć do podklasy BroadcastReceiver. System Android obsługuje przychodzące połączenia SIP i gdy odbiera połączenie, rozgłasza intencję „połączenie przychodzące” (zgodnie z definicją w aplikacji). Oto podklasyfikowany kod BroadcastReceiver z przykładu 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();
            }
        }
    }
}

Konfigurowanie filtra intencji pod kątem odbierania połączeń

Gdy usługa SIP otrzymuje nowe połączenie, wysyła intencję z ciągiem tekstowym podanym przez aplikację. W SipDemo ten ciąg działań to android.SipDemo.INCOMING_CALL.

Ten fragment kodu z SipDemo pokazuje, jak jest tworzony obiekt SipProfile z oczekującą intencją na podstawie ciągu znaków działania android.SipDemo.INCOMING_CALL. Obiekt PendingIntent wykona komunikat, gdy SipProfile otrzyma połączenie:

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

Transmisja zostanie przechwycona przez filtr intencji, który następnie uruchomi odbiornik (IncomingCallReceiver). Filtr intencji możesz określić w pliku manifestu aplikacji lub zrobić to w kodzie, tak jak w przykładowej metodzie onCreate() SipDemo w Activity aplikacji:

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

Testowanie aplikacji SIP

Aby przetestować aplikacje SIP, potrzebujesz:

  • Urządzenie mobilne z systemem Android 2.3 lub nowszym. SIP działa przez sieć bezprzewodową, więc musisz przeprowadzić test na rzeczywistym urządzeniu. Testowanie AVD się nie powiedzie.
  • Konto SIP. Jest wielu różnych dostawców usług SIP, którzy oferują konta SIP.
  • Jeśli chcesz nawiązać połączenie, musisz połączyć je z aktywnym kontem SIP.

Aby przetestować aplikację SIP:

  1. Na urządzeniu połącz się z siecią bezprzewodową (Ustawienia > Sieci zwykłe i bezprzewodowe > Wi-Fi > Ustawienia Wi-Fi).
  2. Konfigurowanie urządzenia mobilnego do testowania zgodnie z opisem w sekcji Programowanie na urządzeniu.
  3. Uruchom aplikację na urządzeniu mobilnym zgodnie z opisem w sekcji Programowanie na urządzeniu.
  4. Jeśli używasz Android Studio, dane wyjściowe dziennika aplikacji możesz wyświetlić, otwierając konsolę dziennika zdarzeń (Widok > Narzędzia Okna > Dziennik zdarzeń).
  5. Sprawdź, czy aplikacja jest skonfigurowana tak, aby automatycznie uruchamiała Logcat po uruchomieniu:
    1. Wybierz Uruchom > Edytuj konfiguracje.
    2. Wybierz kartę Różne w oknie Konfiguracja uruchamiania/debugowania.
    3. W sekcji Logcat wybierz Show logcat automatycznie, a potem OK.