Обнаружение 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.  На схеме выше: * Устройство имеет два логических слота. * В физическом слоте 0 находится физическая карта UICC с активным профилем. * В физическом слоте 2 находится eUICC с активным профилем. * Физический слот 1 сейчас не используется.  На схеме выше: * Устройство имеет три логических слота. * В физическом слоте 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_SIPandroid.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-приложение:
- Подключитесь к беспроводной сети (Настройки > Беспроводные сети > Wi-Fi > Настройки Wi-Fi).
- Настройте мобильное устройство для тестирования, как описано в этой статье.
- Запустите приложение на мобильном устройстве, как описано в руководстве по разработке для устройств.
- Если вы используете Android Studio, то можете посмотреть журнал приложения, открыв консоль Event Log (View > Tool Windows > Event Log).
- Убедитесь, что приложение настроено на автоматический запуск Logcat при выполнении:
- Выберите Run (Запустить) > Edit Configurations (Изменить конфигурации).
- В окне Run/Debug Configurations (Конфигурации запуска/отладки) выберите вкладку Miscellaneous (Разное).
- В разделе Logcat выберите Show logcat automatically (Показывать logcat автоматически) и нажмите OK.