Реализация управления eSIM через мобильное приложение
eSIM Management в мобильном приложении — это не просто показать список профилей. Это работа с локальным профайловым агентом (LPA) устройства через стандарт GSMA SGP.22, балансирование между SM-DP+ сервером оператора и ограничениями iOS/Android на доступ к eUICC, и обработка многоуровневых ошибок активации, которые платформа обычно скрывает за невнятным «Connection error».
Стандарты и ограничения платформ
GSMA SGP.22 (Consumer Profile) — стандарт для потребительских устройств. SGP.32 (IoT) — для M2M-модулей. Мобильные приложения работают с SGP.22.
iOS ограничения серьёзны. Apple не предоставляет публичный API для программного управления eSIM-профилями. Доступны только:
-
CoreTelephony.CTCarrier— информация об активном операторе (без управления профилями) - QR-код активация через
com.apple.esimURL scheme — открывает системный UI - Carrier App entitlement — только операторы с дополнительным соглашением с Apple
Для carrier-grade приложения (приложение оператора связи): com.apple.developer.networking.multipath + специальный entitlement от Apple, доступный только через Mobile Network Operator Program.
Android — значительно открытее. EuiccManager API (Android 9+) доступен для операторов через системный permission android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS. Для обычных приложений — недоступен. Но устройства с LPA in Application реализацией позволяют приложениям взаимодействовать с LPA через Intent-based API.
Android EuiccManager: операторский путь
val euiccManager = getSystemService(Context.EUICC_SERVICE) as EuiccManager
if (!euiccManager.isEnabled) {
showError("eSIM не поддерживается на этом устройстве")
return
}
// Загрузка профиля по активационному коду
val switchIntent = Intent("android.telephony.euicc.action.START_EUICC_ACTIVATION")
switchIntent.putExtra("activation_code", "LPA:1\$smdp.example.com\$ACTIVATION_CODE")
startActivityForResult(switchIntent, REQUEST_CODE_ESIM_DOWNLOAD)
Прямой доступ через downloadSubscription() — только с WRITE_EMBEDDED_SUBSCRIPTIONS, которое сигнатурно привязано к оператору или выдаётся через Device Policy Controller:
// Только для carrier-privileged приложений
val result = euiccManager.downloadSubscription(
DownloadableSubscription.forActivationCode("LPA:1\$smdp.example.com\$CODE"),
switchAfterDownload = true,
cancelSignal = cancellationSignal,
executor = mainExecutor
) { resultCode, extras ->
when (resultCode) {
EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK -> onSuccess()
EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR -> {
// Требуется пользовательское действие — показываем системный диалог
euiccManager.startResolutionActivity(activity, extras, pendingIntent)
}
EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_ERROR -> {
val detailedCode = extras?.getInt(EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE)
handleError(detailedCode)
}
}
}
EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR — самый важный код. Означает, что платформа знает как решить проблему (подтверждение пользователя, аутентификация), но нужен системный UI. Не пытаться обойти — показывать startResolutionActivity.
SM-DP+ серверная сторона
Мобильное приложение — тонкий клиент. Основная логика — на SM-DP+ (Subscription Management Data Preparation+) сервере оператора. Он хранит профили, генерирует activation codes, управляет lifecycle:
Приложение → Backend API → SM-DP+ Server → eUICC (через LPA на устройстве)
Activation Code формат (SGP.22): LPA:1$<SM-DP+ FQDN>$<Matching ID>[$<OID>[$<Confirmation Code Required>]]
Backend генерирует уникальный Matching ID для каждой активации — это одноразовый токен, привязан к конкретному ICCID. После использования становится невалидным.
Информация о профилях без привилегий
Для приложений без carrier-привилегий — читаем то, что доступно:
// Список SIM-карт (включая eSIM профили)
val subscriptionManager = getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) as SubscriptionManager
val activeSubscriptions = subscriptionManager.activeSubscriptionInfoList
activeSubscriptions?.forEach { sub ->
Log.d("eSIM", "Оператор: ${sub.carrierName}, SIM slot: ${sub.simSlotIndex}, eSIM: ${sub.isEmbedded}")
}
isEmbedded = true — профиль на eUICC. Переключение между активными профилями без привилегий — невозможно на Android. На iOS — вообще не доступно программно.
Сроки
Приложение для отображения eSIM-статуса и активации через QR/activation code (Intent-based, без системных привилегий): 2–4 недели. Полноценное carrier-приложение с WRITE_EMBEDDED_SUBSCRIPTIONS и интеграцией с SM-DP+ API: 1–3 месяца. Сроки сильно зависят от доступа к оператору и SM-DP+ окружению.







