Протокол установления сеанса

Обнаружение 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-картой у него будет только один логический слот. * Идентификатор карты: уникальный идентификатор, используемый для идентификации 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 (Multiple Enabled Profiles).

Протокол установления сеанса

В 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, используйте в качестве основы одну из многочисленных современных альтернатив ПО с открытым исходным кодом. Вы также можете использовать API ConnectionService, чтобы интегрировать такие звонки в приложение "Телефон" на устройстве.

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

Ниже приведены классы и один интерфейс (SipRegistrationListener), которые входят в Android SIP API.

Класс/интерфейс Описание
SipAudioCall Обрабатывает голосовой вызов через интернет по протоколу SIP.
SipAudioCall.Listener Прослушиватель событий, связанных с SIP-вызовом, например при получении вызова ("on ringing") или при исходящем вызове ("on calling").
SipErrorCode Определяет коды ошибок, возвращаемые во время действий SIP.
SipManager Предоставляет API для задач SIP, таких как инициирование подключений SIP, и обеспечивает доступ к связанным сервисам SIP.
SipProfile Определяет профиль SIP, включая аккаунт SIP, домен и информацию о сервере.
SipProfile.Builder Вспомогательный класс для создания SipProfile.
SipSession Представляет сеанс SIP, связанный с диалогом SIP или отдельной транзакцией вне диалога.
SipSession.Listener Прослушиватель событий, связанных с сеансом SIP, например когда сеанс регистрируется ("on registering") или выполняется исходящий вызов ("on calling").
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>.

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

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

Создание SipManager

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

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

Чтобы создать экземпляр SipManager, используйте следующий код:

Kotlin

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

Java

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

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

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

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

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

Чтобы создать объект SipProfile, используйте следующий код:

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

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

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

Наконец, этот код устанавливает SipRegistrationListener для SipManager. Этот параметр показывает, было ли устройство SipProfile успешно зарегистрировано у вашего поставщика услуг 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.");
    }
}

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

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

Как сделать голосовой вызов

Чтобы совершить голосовой вызов, необходимо следующее:

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

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

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

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

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

Пример:

Kotlin

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

Java

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.

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

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

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

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

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

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

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

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

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

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

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

  1. Подключитесь к беспроводной сети (Настройки > Беспроводные сети > Wi-Fi > Настройки Wi-Fi).
  2. Настройте мобильное устройство для тестирования, как описано в этой статье.
  3. Запустите приложение на мобильном устройстве, как описано в руководстве по разработке для устройств.
  4. Если вы используете Android Studio, то можете посмотреть журнал приложения, открыв консоль Event Log (View > Tool Windows > Event Log).
  5. Убедитесь, что приложение настроено на автоматический запуск Logcat при выполнении:
    1. Выберите Run (Запустить) > Edit Configurations (Изменить конфигурации).
    2. В окне Run/Debug Configurations (Конфигурации запуска/отладки) выберите вкладку Miscellaneous (Разное).
    3. В разделе Logcat выберите Show logcat automatically (Показывать logcat автоматически) и нажмите OK.