Обзор протокола инициации сеанса

Обнаружение eSIM и SIM-карт

Обнаружение карт

Устройства Android с SIM-картами и eSIM используют следующие идентификаторы в API телефонии, включая [`TelephonyManager`](/reference/android/telephony/TelephonyManager) и [`SubscriptionManager`](/reference/android/telephony/SubscriptionManager): * Идентификатор подписки: уникальный идентификатор мобильной подписки. * Индекс или идентификатор логического слота: уникальный индекс, относящийся к логическому слоту для SIM-карты. Идентификаторы логических слотов начинаются с 0 и увеличиваются в зависимости от количества поддерживаемых активных слотов на устройстве. Например, устройство с двумя SIM-картами обычно имеет слот 0 и слот 1. Если устройство имеет несколько физических слотов, но поддерживает только один активный слот, оно будет иметь только идентификатор логического слота 0. * Индекс или идентификатор физического слота: уникальный индекс, относящийся к в физический слот SIM. Идентификаторы физических слотов начинаются с 0 и увеличиваются в зависимости от количества физических слотов на устройстве. Это отличается от количества логических слотов, которые имеет устройство, которое соответствует количеству активных слотов, которые устройство может использовать. Например, устройство, которое переключается между режимом двух SIM-карт и режимом одной SIM-карты, всегда может иметь два физических слота, но в режиме одной SIM-карты оно будет иметь только один логический слот. * Идентификатор карты: уникальный идентификатор, используемый для идентификации карты UiccCard. ![Схема использования идентификаторов в случае с двумя логическими слотами и тремя физическими слотами](/images/guide/topics/connectivity/tel-ids.png) На приведенной выше схеме: * Устройство имеет два логических слота. * В физическом слоте 0 находится физическая карта UICC с активным профилем. * В физическом слоте 2 находится карта eUICC с активным профилем. * Физический слот 1 в настоящее время не используется. ![Схема использования идентификаторов в случае с тремя логическими слотами и двумя физическими слотами](/images/guide/topics/connectivity/tel-ids-2.png) На приведенной выше схеме: * Устройство имеет три логических слота. слоты. * В физическом слоте 0 находится физическая карта UICC с активным профилем. * В физическом слоте 1 находится карта eUICC, имеющая два загруженных профиля, оба активны с использованием MEP (несколько включенных профилей).

Обзор протокола инициации сеанса

Android предоставляет API, который поддерживает протокол инициации сеанса (SIP). Это позволяет добавлять в ваши приложения функции интернет-телефонии на основе SIP. Android включает в себя полный стек протоколов SIP и интегрированные службы управления вызовами, которые позволяют приложениям легко настраивать исходящие и входящие голосовые вызовы без необходимости напрямую управлять сеансами, связью на транспортном уровне или записывать или воспроизводить звук.

Вот примеры типов приложений, которые могут использовать SIP API:

  • Видеоконференции
  • Мгновенный обмен сообщениями

Требования и ограничения

Вот требования для разработки SIP-приложения:

  • У вас должно быть мобильное устройство под управлением Android 2.3 или выше.
  • SIP работает через беспроводное соединение для передачи данных, поэтому ваше устройство должно иметь соединение для передачи данных (с помощью службы мобильной передачи данных или Wi-Fi). Это означает, что вы не можете тестировать на AVD — вы можете тестировать только на физическом устройстве. Подробности см. в разделе «Тестирование SIP-приложений» .
  • Каждый участник сеанса связи приложения должен иметь SIP-аккаунт. Существует множество различных провайдеров SIP, которые предлагают учетные записи SIP.

Примечание. Библиотека android.net.sip не поддерживает видеозвонки. Если вы хотите реализовать VOIP-вызовы с использованием стека SIP, такого как android.net.sip , рассмотрите одну из многих современных альтернатив с открытым исходным кодом в качестве основы для любой реализации VOIP-вызовов. Альтернативно вы можете реализовать API ConnectionService , чтобы обеспечить тесную интеграцию этих вызовов в приложение Dialer устройства.

Классы и интерфейсы SIP API

Вот краткое описание классов и одного интерфейса ( SipRegistrationListener ), включенных в Android SIP API:

Класс/Интерфейс Описание
SipAudioCall Обрабатывает аудиовызовы через Интернет через SIP.
SipAudioCall.Listener Прослушиватель событий, связанных с вызовом SIP, например, когда вызов принимается («при ​​звонке») или исходящий вызов («при ​​вызове»).
SipErrorCode Определяет коды ошибок, возвращаемые во время действий SIP.
SipManager Предоставляет API для задач SIP, таких как инициирование SIP-соединений, и обеспечивает доступ к связанным службам SIP.
SipProfile Определяет профиль SIP, включая учетную запись SIP, информацию о домене и сервере.
SipProfile.Builder Вспомогательный класс для создания SipProfile.
SipSession Представляет сеанс SIP, связанный с диалоговым окном SIP, или отдельную транзакцию, не входящую в диалог.
SipSession.Listener Прослушиватель событий, связанных с сеансом SIP, например, когда сеанс регистрируется («при ​​регистрации») или исходящий вызов («при ​​вызове»).
SipSession.State Определяет состояния сеанса SIP, такие как «регистрация», «исходящий вызов» и «в вызове».
SipRegistrationListener Интерфейс, который является прослушивателем событий регистрации SIP.

Создание манифеста

Если вы разрабатываете приложение, использующее SIP API, помните, что эта функция поддерживается только на Android 2.3 (уровень API 9) и более поздних версиях платформы. Кроме того, среди устройств под управлением Android 2.3 (уровень API 9) или выше не все устройства поддерживают SIP.

Чтобы использовать SIP, добавьте следующие разрешения в манифест вашего приложения:

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

Чтобы гарантировать, что ваше приложение можно будет установить только на устройства, поддерживающие SIP, добавьте в манифест вашего приложения следующее:

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

Это означает, что вашему приложению требуется Android 2.3 или выше. Дополнительные сведения см. в разделе «Уровни API» и документации по элементу <uses-sdk> .

Чтобы контролировать, как ваше приложение фильтруется с устройств, не поддерживающих SIP (например, в Google Play), добавьте в манифест вашего приложения следующее:

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

Это означает, что ваше приложение использует SIP API. Объявление должно включать атрибут android:required , который указывает, хотите ли вы, чтобы приложение фильтровалось от устройств, не поддерживающих SIP. В зависимости от вашей реализации могут также потребоваться и другие объявления <uses-feature> . Дополнительные сведения см. в документации по элементу <uses-feature> .

Если ваше приложение предназначено для приема вызовов, вы также должны определить получателя (подкласс BroadcastReceiver ) в манифесте приложения:

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

Вот выдержки из манифеста 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>

Создание СипМенеджера

Чтобы использовать SIP API, ваше приложение должно создать объект SipManager . SipManager позаботится о следующем в вашем приложении:

  • Инициирование SIP-сессий.
  • Инициирование и прием звонков.
  • Регистрация и отмена регистрации у SIP-провайдера.
  • Проверка подключения сеанса.

Вы создаете новый экземпляр SipManager следующим образом:

Котлин

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

Ява

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

Регистрация на SIP-сервере

Типичное SIP-приложение Android включает одного или нескольких пользователей, каждый из которых имеет учетную запись SIP. В приложении Android SIP каждая учетная запись SIP представлена ​​объектом SipProfile .

SipProfile определяет профиль SIP, включая учетную запись SIP, а также информацию о домене и сервере. Профиль, связанный с учетной записью SIP на устройстве, на котором запущено приложение, называется локальным профилем . Профиль, к которому подключен сеанс, называется профилем узла . Когда ваше SIP-приложение входит в систему SIP-сервера с локальным SipProfile , это фактически регистрирует устройство как место для отправки SIP-вызовов для вашего SIP-адреса.

В этом разделе показано, как создать SipProfile , зарегистрировать его на SIP-сервере и отслеживать события регистрации.

Вы создаете объект SipProfile следующим образом:

Котлин

private var sipProfile: SipProfile? = null
...

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

Ява

public SipProfile sipProfile = null;
...

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

Следующий фрагмент кода открывает локальный профиль для совершения вызовов и/или приема обычных вызовов SIP. Вызывающая сторона может совершать последующие вызовы через mSipManager.makeAudioCall . Этот отрывок также устанавливает действие android.SipDemo.INCOMING_CALL , которое будет использоваться фильтром намерений, когда устройство получает вызов (см. Настройка фильтра намерений для приема вызовов ). Это этап регистрации:

Котлин

val intent = Intent("android.SipDemo.INCOMING_CALL")
val pendingIntent: PendingIntent = PendingIntent.getBroadcast(this, 0, intent, Intent.FILL_IN_DATA)
sipManager?.open(sipProfile, pendingIntent, 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);

Наконец, этот код устанавливает SipRegistrationListener в SipManager . Это отслеживает, был ли SipProfile успешно зарегистрирован у вашего поставщика услуг SIP:

Котлин

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

Ява

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

Когда ваше приложение закончит использовать профиль, оно должно закрыть его, чтобы освободить связанные объекты в памяти и отменить регистрацию устройства на сервере. Например:

Котлин

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

Ява

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

Выполнение аудиовызова

Для совершения аудиовызова у вас должно быть следующее:

  • SipProfile , который выполняет вызов («локальный профиль»), и действительный SIP-адрес для приема вызова («профиль узла»).
  • Объект SipManager .

Чтобы совершить аудиовызов, вам следует настроить SipAudioCall.Listener . Большая часть взаимодействия клиента со стеком SIP происходит через прослушиватели. В этом фрагменте вы видите, как SipAudioCall.Listener настраивает ситуацию после установления вызова:

Котлин

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

Ява

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

После настройки SipAudioCall.Listener вы можете совершить вызов. Метод SipManager makeAudioCall принимает следующие параметры:

  • Локальный профиль SIP (вызывающая сторона).
  • Одноранговый профиль SIP (вызываемого пользователя).
  • SipAudioCall.Listener для прослушивания событий вызова от SipAudioCall . Это может быть null , но, как показано выше, прослушиватель используется для настройки после установления вызова.
  • Значение тайм-аута в секундах.

Например:

Котлин

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

Ява

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

Прием звонков

Чтобы принимать вызовы, приложение SIP должно включать подкласс BroadcastReceiver , способный реагировать на намерение, указывающее на наличие входящего вызова. Таким образом, в вашем приложении вам необходимо сделать следующее:

  • В AndroidManifest.xml объявите <receiver> . В SipDemo это <receiver android:name=".IncomingCallReceiver" android:label="Call Receiver" /> .
  • Реализуйте приемник, который является подклассом BroadcastReceiver . В SipDemo это IncomingCallReceiver .
  • Инициализируйте локальный профиль ( SipProfile ) с ожидающим намерением, которое запускает ваш приемник, когда кто-то вызывает локальный профиль.
  • Настройте фильтр намерений, который фильтрует по действию, представляющему входящий вызов. В SipDemo это действие — android.SipDemo.INCOMING_CALL .

Создание подкласса BroadcastReceiver

Чтобы принимать вызовы, ваше SIP-приложение должно быть подклассом BroadcastReceiver . Система Android обрабатывает входящие вызовы SIP и транслирует «входящий вызов». намерение (как определено приложением) при получении вызова. Вот код BroadcastReceiver с подклассом из примера SipDemo .

Котлин

/**
 * 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()
            }
        }
    }
}

Ява

/**
 * 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();
            }
        }
    }
}

Настройка фильтра намерений для приема звонков

Когда служба SIP получает новый вызов, она отправляет намерение со строкой действия, предоставленной приложением. В SipDemo эта строка действия — android.SipDemo.INCOMING_CALL .

Этот фрагмент кода из SipDemo показывает, как создается объект SipProfile с ожидающим намерением на основе строки действия android.SipDemo.INCOMING_CALL . Объект PendingIntent выполнит широковещательную рассылку, когда SipProfile получит вызов:

Котлин

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)

Ява

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

Трансляция будет перехвачена фильтром намерений, который затем активирует приемник ( IncomingCallReceiver ). Вы можете указать фильтр намерений в файле манифеста вашего приложения или сделать это в коде, как в методе onCreate() приложения SipDemo в Activity приложения:

Котлин

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

Ява

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

Тестирование SIP-приложений

Для тестирования SIP-приложений вам потребуется следующее:

  • Мобильное устройство под управлением Android 2.3 или более поздней версии. SIP работает по беспроводной сети, поэтому вам необходимо протестировать его на реальном устройстве. Тестирование на AVD не сработает.
  • SIP-аккаунт. Существует множество различных провайдеров SIP, которые предлагают учетные записи SIP.
  • Если вы совершаете звонок, он также должен быть направлен на действующую учетную запись SIP.

Чтобы протестировать SIP-приложение:

  1. На своем устройстве подключитесь к беспроводной сети ( Настройки > Беспроводные сети > Wi-Fi > Настройки Wi-Fi ).
  2. Настройте мобильное устройство для тестирования, как описано в разделе «Разработка на устройстве» .
  3. Запустите приложение на своем мобильном устройстве, как описано в разделе «Разработка на устройстве» .
  4. Если вы используете Android Studio, вы можете просмотреть выходные данные журнала приложения, открыв консоль журнала событий ( Просмотр > Инструменты Windows > Журнал событий ).
  5. Убедитесь, что ваше приложение настроено на автоматический запуск Logcat при запуске:
    1. Выберите «Выполнить» > «Изменить конфигурации» .
    2. Выберите вкладку «Разное» в окне «Конфигурации запуска/отладки» .
    3. В разделе Logcat выберите «Показывать logcat автоматически» , затем нажмите «ОК» .