Как реализовать поддержку eSIM

Технология встроенной SIM-карты (eSIM или eUICC) позволяет пользователям мобильных устройств скачивать профиль оператора и активировать его услуги без физической SIM-карты. Это глобальная спецификация, разработанная GSMA, которая позволяет удаленно инициализировать SIM-карту на любом мобильном устройстве. Начиная с Android 9, фреймворк Android предоставляет стандартные API для доступа к eSIM и управления профилями подписки на ней. Эти API eUICC позволяют сторонним разработчикам создавать собственные приложения операторов и помощников по локальным профилям (LPA) для устройств Android с поддержкой eSIM.

LPA – это отдельное системное приложение, которое должно быть включено в образ сборки Android. Управление профилями на eSIM обычно осуществляется LPA, поскольку он служит мостом между SM-DP+ (удаленным сервисом, который подготавливает, хранит и доставляет пакеты профилей на устройства) и чипом eUICC. APK-файл LPA может включать компонент пользовательского интерфейса, называемый LPA UI или LUI, который предоставляет конечному пользователю централизованное место для управления всеми встроенными профилями подписки. Платформа Android автоматически обнаруживает и подключается к лучшему доступному LPA и направляет все операции eUICC через экземпляр LPA.

Упрощенная архитектура удаленной инициализации SIM-карт (RSP)

Рисунок 1. Упрощенная архитектура RSP

Операторы мобильной связи, которые хотят создать приложение оператора, могут использовать API в EuiccManager, который предоставляет высокоуровневые операции управления профилем, такие как downloadSubscription(), switchToSubscription() и deleteSubscription().

Если вы производитель устройств и хотите создать собственное системное приложение LPA, вам нужно расширить EuiccService для фреймворка Android, чтобы подключиться к сервисам LPA. Кроме того, вам следует использовать API в EuiccCardManager, которые предоставляют функции ES10x на основе GSMA RSP v2.0. Эти функции используются для отправки команд на чип eUICC, например prepareDownload(), loadBoundProfilePackage(), retrieveNotificationList() и resetMemory().

Для работы API в EuiccManager требуется правильно реализованное приложение LPA, а вызывающий API EuiccCardManager должен быть LPA. Это ограничение применяется фреймворком Android.

Устройства с Android 10 и более поздних версий могут поддерживать устройства с несколькими eSIM. Подробнее о поддержке нескольких eSIM…

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

API eUICC в Android 9 позволяют операторам мобильной сети создавать фирменные приложения для управления профилями. В том числе скачивать и удалять профили подписок, принадлежащие оператору, а также переключаться на профиль, принадлежащий оператору.

EuiccManager

EuiccManager – это основная точка входа для взаимодействия приложений с LPA. Это касается приложений операторов связи, которые скачивают, удаляют и переключают подписки, принадлежащие оператору. К ним относится и системное приложение LUI, которое предоставляет централизованное местоположение или интерфейс для управления всеми встроенными подписками и может быть отдельным приложением от того, которое предоставляет EuiccService.

Чтобы использовать общедоступные API, приложение оператора связи должно сначала получить экземпляр EuiccManager через Context#getSystemService:

EuiccManager mgr = (EuiccManager) context.getSystemService(Context.EUICC_SERVICE);

Прежде чем выполнять какие-либо операции с eSIM, убедитесь, что она поддерживается на устройстве. EuiccManager#isEnabled() обычно возвращает значение true, если определена функция android.hardware.telephony.euicc и присутствует пакет LPA.

if (mgr == null || !mgr.isEnabled()) {
    return;
}

Чтобы получить информацию об аппаратном обеспечении eUICC и версии ОС eSIM:

EuiccInfo info = mgr.getEuiccInfo();
String osVer = info.getOsVersion();

Многие API, например downloadSubscription() и switchToSubscription(), используют обратные вызовы PendingIntent, поскольку их выполнение может занять несколько секунд или даже минут. PendingIntent отправляется с кодом результата в пространстве EuiccManager#EMBEDDED_SUBSCRIPTION_RESULT_, в котором содержатся коды ошибок, определенные фреймворком, а также произвольный подробный код результата, переданный из LPA как EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE, что позволяет приложению оператора отслеживать его для целей ведения журнала и отладки. Обратный вызов PendingIntent должен быть BroadcastReceiver.

Чтобы скачать подписку, созданную с помощью кода активации или QR-кода:

// Register receiver.
static final String ACTION_DOWNLOAD_SUBSCRIPTION = "download_subscription";
static final String LPA_DECLARED_PERMISSION
    = "com.your.company.lpa.permission.BROADCAST";
BroadcastReceiver receiver =
        new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                if (!action.equals(intent.getAction())) {
                    return;
                }
                resultCode = getResultCode();
                detailedCode = intent.getIntExtra(
                    EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,
                    0 /* defaultValue*/);

                // If the result code is a resolvable error, call startResolutionActivity
                if (resultCode == EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR) {
                    PendingIntent callbackIntent = PendingIntent.getBroadcast(
                        getContext(), 0 /* requestCode */, intent,
                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
                    mgr.startResolutionActivity(
                        activity,
                        0 /* requestCode */,
                        intent,
                        callbackIntent);
                }

                resultIntent = intent;
            }
        };
context.registerReceiver(receiver,
        new IntentFilter(ACTION_DOWNLOAD_SUBSCRIPTION),
        LPA_DECLARED_PERMISSION /* broadcastPermission*/,
        null /* handler */);

// Download subscription asynchronously.
DownloadableSubscription sub = DownloadableSubscription
        .forActivationCode(code /* encodedActivationCode*/);
Intent intent = new Intent(action).setPackage(context.getPackageName());
PendingIntent callbackIntent = PendingIntent.getBroadcast(
        getContext(), 0 /* requestCode */, intent,
        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
mgr.downloadSubscription(sub, true /* switchAfterDownload */,
        callbackIntent);

Определите и используйте разрешение в AndroidManifest.xml:

    <permission android:protectionLevel="signature" android:name="com.your.company.lpa.permission.BROADCAST" />
    <uses-permission android:name="com.your.company.lpa.permission.BROADCAST"/>

Чтобы перейти на подписку, используя ее идентификатор:

// Register receiver.
static final String ACTION_SWITCH_TO_SUBSCRIPTION = "switch_to_subscription";
static final String LPA_DECLARED_PERMISSION
    = "com.your.company.lpa.permission.BROADCAST";
BroadcastReceiver receiver =
        new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                if (!action.equals(intent.getAction())) {
                    return;
                }
                resultCode = getResultCode();
                detailedCode = intent.getIntExtra(
                    EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,
                    0 /* defaultValue*/);
                resultIntent = intent;
            }
        };
context.registerReceiver(receiver,
        new IntentFilter(ACTION_SWITCH_TO_SUBSCRIPTION),
        LPA_DECLARED_PERMISSION /* broadcastPermission*/,
        null /* handler */);

// Switch to a subscription asynchronously.
Intent intent = new Intent(action).setPackage(context.getPackageName());
PendingIntent callbackIntent = PendingIntent.getBroadcast(
        getContext(), 0 /* requestCode */, intent,
        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
mgr.switchToSubscription(1 /* subscriptionId */, callbackIntent);

Полный список EuiccManager API и примеры кода можно найти в разделе eUICC API.

Устранимые ошибки

В некоторых случаях система не может выполнить операцию с eSIM, но пользователь может устранить ошибку. Например, метод downloadSubscription может не сработать, если в метаданных профиля указано, что требуется код подтверждения оператора. Или switchToSubscription может завершиться ошибкой, если у приложения оператора есть права оператора в отношении целевого профиля (то есть оператор владеет профилем), но нет прав оператора в отношении текущего включенного профиля, и поэтому требуется согласие пользователя.

В таких случаях вызывается обратный вызов вызывающего абонента с параметром EuiccManager#EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR. Обратный вызов Intent содержит внутренние дополнительные данные, поэтому, когда вызывающий абонент передает его в EuiccManager#startResolutionActivity, разрешение можно запросить через LUI. Если снова использовать пример с кодом подтверждения, EuiccManager#startResolutionActivity активирует экран LUI, на котором пользователь может ввести код подтверждения. После ввода кода операция скачивания возобновляется. При таком подходе приложение оператора связи полностью контролирует, когда показывать интерфейс, а LPA/LUI получает расширяемый метод для добавления новых способов устранения проблем, которые может решить пользователь, без необходимости изменять клиентские приложения.

В Android 9 определены следующие устранимые ошибки в EuiccService, которые должен обрабатывать LUI:

/**
 * Alert the user that this action will result in an active SIM being
 * deactivated. To implement the LUI triggered by the system, you need to define
 * this in AndroidManifest.xml.
 */
public static final String ACTION_RESOLVE_DEACTIVATE_SIM =
        "android.service.euicc.action.RESOLVE_DEACTIVATE_SIM";
/**
 * Alert the user about a download/switch being done for an app that doesn't
 * currently have carrier privileges.
 */
public static final String ACTION_RESOLVE_NO_PRIVILEGES =
        "android.service.euicc.action.RESOLVE_NO_PRIVILEGES";

/** Ask the user to resolve all the resolvable errors. */
public static final String ACTION_RESOLVE_RESOLVABLE_ERRORS =
        "android.service.euicc.action.RESOLVE_RESOLVABLE_ERRORS";

Права оператора связи

Если вы оператор связи и разрабатываете собственное приложение, которое вызывает EuiccManager, чтобы загружать профили на устройство, в метаданные профиля должны быть включены правила привилегий оператора связи, соответствующие вашему приложению. Это связано с тем, что профили подписок, принадлежащие разным операторам, могут сосуществовать в eUICC устройства, и каждое приложение оператора должно иметь доступ только к профилям, принадлежащим этому оператору. Например, оператор А не должен иметь возможности скачать, включить или отключить профиль, принадлежащий оператору Б.

Чтобы профиль был доступен только его владельцу, Android использует механизм предоставления специальных прав приложению владельца профиля (то есть приложению оператора). Платформа Android загружает сертификаты, хранящиеся в файле правил доступа (ARF) профиля, и предоставляет приложениям, подписанным этими сертификатами, разрешение на вызовы API EuiccManager. Ниже описан общий процесс.

  1. Оператор подписывает APK-файл приложения оператора связи. Инструмент apksigner прикрепляет к APK-файлу сертификат открытого ключа.
  2. Оператор/SM-DP+ готовит профиль и его метаданные, в том числе файл ARF, который содержит:

    1. Подпись (SHA-1 или SHA-256) сертификата открытого ключа приложения оператора (обязательно).
    2. Название пакета приложения оператора связи (настоятельно рекомендуется)
  3. Приложение оператора пытается выполнить операцию eUICC с помощью API EuiccManager.

  4. Платформа Android проверяет, совпадает ли хеш SHA-1 или SHA-256 сертификата вызывающего приложения с подписью сертификата, полученного из файла ARF целевого профиля. Если название пакета приложения оператора включено в ARF, оно также должно совпадать с названием пакета вызывающего приложения.

  5. После проверки подписи и названия пакета (если оно указано) вызывающее приложение получает привилегии оператора в целевом профиле.

Поскольку метаданные профиля могут быть доступны за пределами самого профиля (чтобы LPA мог получить метаданные профиля из SM-DP+ до того, как профиль будет скачан, или из ISD-R, когда профиль отключен), они должны содержать те же правила привилегий оператора, что и в профиле.

ОС eUICC и SM-DP+ должны поддерживать проприетарный тег BF76 в метаданных профиля. Содержимое тега должно соответствовать правилам доступа оператора, возвращаемым апплетом правил доступа (ARA), определенным в UICC Carrier Privileges:

RefArDo ::= [PRIVATE 2] SEQUENCE {  -- Tag E2
    refDo [PRIVATE 1] SEQUENCE {  -- Tag E1
        deviceAppIdRefDo [PRIVATE 1] OCTET STRING (SIZE(20|32)),  -- Tag C1
        pkgRefDo [PRIVATE 10] OCTET STRING (SIZE(0..127)) OPTIONAL  -- Tag CA
    },
    arDo [PRIVATE 3] SEQUENCE {  -- Tag E3
        permArDo [PRIVATE 27] OCTET STRING (SIZE(8))  -- Tag DB
    }
}

Подробнее о том, как подписывать приложения… Подробнее о привилегиях оператора связи на карте UICC…

Как создать приложение-помощник для местного профиля

Производители устройств могут реализовать собственного локального помощника профиля (LPA), который должен быть подключен к API Euicc Android. В следующих разделах кратко описано, как создать приложение LPA и интегрировать его с системой Android.

Требования к оборудованию и модему

LPA и ОС eSIM на чипе eUICC должны поддерживать как минимум GSMA RSP (Remote SIM Provisioning) версии 2.0 или 2.2. Также следует использовать серверы SM-DP+ и SM-DS с соответствующей версией RSP. Подробную информацию об архитектуре RSP можно найти в спецификации GSMA SGP.21 RSP Architecture Specification.

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

  • IRadio HAL v1.1: setSimPower
  • IRadio HAL v1.2: getIccCardStatus

  • IRadioConfig HAL v1.0: getSimSlotsStatus

  • IRadioConfig AIDL v1.0: getAllowedCarriers

    Google LPA должен знать статус блокировки оператором, чтобы разрешать скачивание или перенос eSIM только для разрешенного оператора. В противном случае пользователи могут скачать и перенести SIM-карту, а затем обнаружить, что устройство заблокировано для использования только с определенным оператором.

    • Поставщики или производители оригинального оборудования должны реализовать HAL API IRadioSim.getAllowedCarriers().

    • Поставщик RIL / модема должен заполнить статус блокировки и carrierId оператора, на которого заблокировано устройство, в рамках API HAL IRadioSimResponse.getAllowedCarriersResponse().

Модем должен распознать eSIM с включенным профилем загрузки по умолчанию как действительную SIM-карту и не отключать ее питание.

Для устройств с Android 10 необходимо определить массив идентификаторов слотов eUICC, которые нельзя извлечь. Пример: arrays.xml.

<resources>
   <!-- Device-specific array of SIM slot indexes which are are embedded eUICCs.
        e.g. If a device has two physical slots with indexes 0, 1, and slot 1 is an
        eUICC, then the value of this array should be:
            <integer-array name="non_removable_euicc_slots">
                <item>1</item>
            </integer-array>
        If a device has three physical slots and slot 1 and 2 are eUICCs, then the value of
        this array should be:
            <integer-array name="non_removable_euicc_slots">
               <item>1</item>
               <item>2</item>
            </integer-array>
        This is used to differentiate between removable eUICCs and built in eUICCs, and should
        be set by OEMs for devices which use eUICCs. -->

   <integer-array name="non_removable_euicc_slots">
       <item>1</item>
   </integer-array>
</resources>

Полный список требований к модему приведен в статье Требования к модему для поддержки eSIM.

EuiccService

LPA состоит из двух отдельных компонентов (оба могут быть реализованы в одном APK): серверной части LPA и интерфейса LPA (LUI).

Чтобы реализовать серверную часть LPA, необходимо расширить класс EuiccService и объявить этот сервис в файле манифеста. Сервис должен требовать системное разрешение android.permission.BIND_EUICC_SERVICE, чтобы к нему могла подключаться только система. Сервис также должен содержать фильтр интентов с действием android.service.euicc.EuiccService. Если на устройстве есть несколько реализаций, приоритет фильтра намерений должен быть ненулевым. Пример:

<service
    android:name=".EuiccServiceImpl"
    android:permission="android.permission.BIND_EUICC_SERVICE">
    <intent-filter android:priority="100">
        <action android:name="android.service.euicc.EuiccService" />
    </intent-filter>
</service>

Внутренние компоненты Android определяют активное LPA и взаимодействуют с ним по мере необходимости для поддержки API eUICC Android. PackageManager запрашивает все приложения с разрешением android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS, которое указывает сервис для действия android.service.euicc.EuiccService. Выбирается сервис с наивысшим приоритетом. Если сервис не найден, поддержка LPA отключена.

Чтобы реализовать LUI, вам нужно предоставить действие для следующих задач:

  • android.service.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS
  • android.service.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION

Как и в случае с сервисом, для каждого действия требуется системное разрешение android.permission.BIND_EUICC_SERVICE. Каждый из них должен иметь фильтр интентов с соответствующим действием, категорией android.service.euicc.category.EUICC_UI и ненулевым приоритетом. При выборе реализаций для этих действий используется та же логика, что и при выборе реализации EuiccService. Пример:

<activity android:name=".MyLuiActivity"
          android:exported="true"
          android:permission="android.permission.BIND_EUICC_SERVICE">
    <intent-filter android:priority="100">
        <action android:name="android.service.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS" />
        <action android:name="android.service.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.service.euicc.category.EUICC_UI" />
    </intent-filter>
</activity>

Это означает, что интерфейс, реализующий эти экраны, может быть из другого APK, чем тот, который реализует EuiccService. Решение о том, будет ли у вас один APK-файл или несколько (например, один, реализующий EuiccService, и один, предоставляющий действия LUI), зависит от вашего проекта.

EuiccCardManager

EuiccCardManager – это интерфейс для связи с чипом eSIM. Она предоставляет функции ES10 (как описано в спецификации GSMA RSP) и обрабатывает низкоуровневые команды запроса/ответа APDU, а также синтаксический анализ ASN.1. EuiccCardManager – это системный API, который могут вызывать только приложения с системными правами доступа.

Приложения оператора связи, LPA и Euicc API

Рисунок 2. Приложение оператора и LPA используют Euicc API.

Для вызова API, управляющих профилями, через EuiccCardManager вызывающая сторона должна быть LPA. Это требование фреймворка Android. Это означает, что вызывающий объект должен расширять EuiccService и быть объявлен в файле манифеста, как описано в предыдущих разделах.

Как и в случае с EuiccManager, чтобы использовать API EuiccCardManager, LPA должен сначала получить экземпляр EuiccCardManager через Context#getSystemService:

EuiccCardManager cardMgr = (EuiccCardManager) context.getSystemService(Context.EUICC_CARD_SERVICE);

Чтобы получить все профили на eUICC:

ResultCallback<EuiccProfileInfo[]> callback =
       new ResultCallback<EuiccProfileInfo[]>() {
           @Override
           public void onComplete(int resultCode,
                   EuiccProfileInfo[] result) {
               if (resultCode == EuiccCardManagerReflector.RESULT_OK) {
                   // handle result
               } else {
                   // handle error
               }
           }
       };

cardMgr.requestAllProfiles(eid, AsyncTask.THREAD_POOL_EXECUTOR, callback);

Внутри EuiccCardManager подключается к EuiccCardController (который выполняется в процессе телефона) через интерфейс AIDL, и каждый метод EuiccCardManager получает обратный вызов от процесса телефона через другой, выделенный интерфейс AIDL. При использовании API EuiccCardManager вызывающая сторона (LPA) должна предоставить объект Executor, через который вызывается обратный вызов. Этот объект Executor может выполняться в одном потоке или в пуле потоков по вашему выбору.

Большинство API EuiccCardManager используются одинаково. Например, чтобы загрузить связанный пакет профиля в eUICC, выполните следующие действия:

...
cardMgr.loadBoundProfilePackage(eid, boundProfilePackage,
        AsyncTask.THREAD_POOL_EXECUTOR, callback);

Чтобы переключиться на другой профиль с заданным ICCID:

...
cardMgr.switchToProfile(eid, iccid, true /* refresh */,
        AsyncTask.THREAD_POOL_EXECUTOR, callback);

Чтобы получить адрес SM-DP+ по умолчанию с чипа eUICC:

...
cardMgr.requestDefaultSmdpAddress(eid, AsyncTask.THREAD_POOL_EXECUTOR,
        callback);

Чтобы получить список уведомлений о заданных событиях, выполните следующие действия:

...
cardMgr.listNotifications(eid,
        EuiccNotification.Event.INSTALL
              | EuiccNotification.Event.DELETE /* events */,
        AsyncTask.THREAD_POOL_EXECUTOR, callback);

Как активировать профиль eSIM через приложение оператора связи

На устройствах с Android 9 или более поздней версии можно использовать приложение оператора, чтобы активировать eSIM и скачать профили. Приложение оператора может скачивать профили, вызывая функцию downloadSubscription напрямую или предоставляя код активации LPA.

Когда приложение оператора скачивает профиль, вызывая метод downloadSubscription, вызов обеспечивает управление профилем через BF76 тег метаданных, который кодирует правила привилегий оператора для профиля. Если у профиля нет тега BF76 или если его тег BF76 не соответствует подписи приложения оператора связи, то скачивание отклоняется.

Ниже описано, как активировать eSIM с помощью приложения оператора, используя код активации.

Как активировать eSIM с помощью кода активации

При использовании кода активации для активации профиля eSIM LPA получает код активации из приложения оператора и скачивает профиль. Этот процесс может быть инициирован LPA, который может управлять всем процессом пользовательского интерфейса, то есть пользовательский интерфейс приложения оператора не отображается. Этот подход позволяет обойти проверку тега BF76, и операторам связи не нужно реализовывать весь процесс активации eSIM, включая скачивание профиля eSIM и обработку ошибок.

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

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

Чтобы определить интерфейсы AIDL, создайте следующие файлы AIDL для приложений LPA и оператора.

  • ICarrierEuiccProvisioningService.aidl

    package android.service.euicc;
    
    import android.service.euicc.IGetActivationCodeCallback;
    
    oneway interface ICarrierEuiccProvisioningService {
        // The method to get the activation code from the carrier app. The caller needs to pass in
        // the implementation of IGetActivationCodeCallback as the parameter.
        void getActivationCode(in IGetActivationCodeCallback callback);
    
        // The method to get the activation code from the carrier app. The caller needs to pass in
        // the activation code string as the first parameter and the implementation of
        // IGetActivationCodeCallback as the second parameter. This method provides the carrier
        // app the device EID which allows a carrier to pre-bind a profile to the device's EID before
        // the download process begins.
        void getActivationCodeForEid(in String eid, in IGetActivationCodeCallback callback);
    }
    
    
  • IGetActivationCodeCallback.aidl

    package android.service.euicc;
    
    oneway interface IGetActivationCodeCallback {
        // The call back method needs to be called when the carrier app gets the activation
        // code successfully. The caller needs to pass in the activation code string as the
        // parameter.
        void onSuccess(String activationCode);
    
        // The call back method needs to be called when the carrier app failed to get the
        // activation code.
        void onFailure();
    }
    

Пример реализации LPA

Чтобы связать LPA с реализацией ICarrierEuiccProvisioningService в приложении оператора, скопируйте ICarrierEuiccProvisioningService.aidl и IGetActivationCodeCallback.aidl в свой проект и реализуйте ServiceConnection.

@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
    mCarrierProvisioningService = ICarrierEuiccProvisioningService.Stub.asInterface(iBinder);
}

После привязки к реализации приложения оператора LPA вызывает ICarrierEuiccProvisioningService getActivationCode или getActivationCodeForEid, чтобы получить код активации из приложения оператора, передав реализацию заглушки класса IGetActivationCodeCallback.

Разница между getActivationCode и getActivationCodeForEid заключается в том, что getActivationCodeForEid позволяет оператору предварительно привязать профиль к EID устройства до начала процесса скачивания.

void getActivationCodeFromCarrierApp() {
    IGetActivationCodeCallback.Stub callback =
            new IGetActivationCodeCallback.Stub() {
                @Override
                public void onSuccess(String activationCode) throws RemoteException {
                    // Handle the case LPA success to get activation code from a carrier app.
                }

                @Override
                public void onFailure() throws RemoteException {
                    // Handle the case LPA failed to get activation code from a carrier app.
                }
            };
    
    try {
        mCarrierProvisioningService.getActivationCode(callback);
    } catch (RemoteException e) {
        // Handle Remote Exception
    }
}

Пример реализации для приложения оператора

Чтобы LPA мог подключиться к приложению оператора, оно должно скопировать файлы ICarrierEuiccProvisioningService.aidl и IGetActivationCodeCallback.aidl в ваш проект и объявить службу ICarrierEuiccProvisioningService в файле AndroidManifest.xml. Сервис должен требовать системное разрешение android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS, чтобы к нему мог подключаться только LPA – приложение с системными привилегиями. Сервис также должен включать фильтр интентов с действием android.service.euicc.action.BIND_CARRIER_PROVISIONING_SERVICE.

  • AndroidManifest.xml

    <application>
      ...
      <service
          android:name=".CarrierEuiccProvisioningService"
          android:exported="true"
          android:permission="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS">
        <intent-filter>
          <action android:name="android.service.euicc.action.BIND_CARRIER_PROVISIONING_SERVICE"/>
        </intent-filter>
      </service>
      ...
    </application>
    

Чтобы реализовать сервис операторского приложения AIDL, создайте сервис, расширьте класс Stub и реализуйте методы getActivationCode и getActivationCodeForEid. Затем LPA может вызвать любой из методов, чтобы получить код активации профиля. Приложение оператора должно ответить вызовом IGetActivationCodeCallback#onSuccess с кодом активации, если код был успешно получен с сервера оператора. Если это не удается, приложение оператора должно возвращать IGetActivationCodeCallback#onFailure.

  • CarrierEuiccProvisioningService.java

    import android.service.euicc.ICarrierEuiccProvisioningService;
    import android.service.euicc.ICarrierEuiccProvisioningService.Stub;
    import android.service.euicc.IGetActivationCodeCallback;
    
    public class CarrierEuiccProvisioningService extends Service {
        private final ICarrierEuiccProvisioningService.Stub binder =
            new Stub() {
              @Override
              public void getActivationCode(IGetActivationCodeCallback callback) throws RemoteException {
                String activationCode = // do whatever work necessary to get an activation code (HTTP requests to carrier server, fetch from storage, etc.)
                callback.onSuccess(activationCode);
              }
    
              @Override
              public void getActivationCodeForEid(String eid, IGetActivationCodeCallback callback) throws RemoteException {
                String activationCode = // do whatever work necessary (HTTP requests, fetch from storage, etc.)
                callback.onSuccess(activationCode);
              }
          }
    }
    

Запуск интерфейса приложения оператора в процессе активации LPA

На устройствах с Android 11 и более поздних версий LPA может запускать интерфейс приложения оператора. Это полезно, поскольку приложению оператора может потребоваться дополнительная информация от пользователя, прежде чем предоставить код активации LPA. Например, операторы могут требовать от пользователей войти в аккаунт, чтобы активировать номер телефона или выполнить другие действия по переносу.

Вот как запустить интерфейс приложения оператора связи в LPA:

  1. LPA запускает процесс активации приложения оператора, отправляя интент android.service.euicc.action.START_CARRIER_ACTIVATION в пакет приложения оператора, содержащий действие. (Чтобы избежать получения интентов от приложений, не относящихся к LPA, получатель приложения оператора должен быть защищен в объявлении манифеста с помощью android:permission="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS").

    String packageName = // The carrier app's package name
    
    Intent carrierAppIntent =
        new Intent(“android.service.euicc.action.START_CARRIER_ACTIVATION”)
            .setPackage(packageName);
    
    ResolveInfo activity =
        context.getPackageManager().resolveActivity(carrierAppIntent, 0);
    
    carrierAppIntent
        .setClassName(activity.activityInfo.packageName, activity.activityInfo.name);
    
    startActivityForResult(carrierAppIntent, requestCode);
    
  2. Приложение оператора выполняет свою работу, используя собственный интерфейс. Например, войти в аккаунт пользователя или отправить HTTP-запросы в серверную часть оператора.

  3. Приложение оператора отвечает на запрос LPA, вызывая функции setResult(int, Intent) и finish().

    1. Если приложение оператора отвечает кодом RESULT_OK, LPA продолжает процесс активации. Если приложение оператора связи определяет, что пользователю нужно отсканировать QR-код, а не разрешать LPA привязывать сервис приложения оператора связи, приложение оператора связи отвечает LPA с помощью setResult(int, Intent) с RESULT_OK и экземпляром Intent, содержащим логическое дополнение android.telephony.euicc.extra.USE_QR_SCANNER, для которого задано значение true. Затем LPA проверяет дополнительную информацию и запускает сканер QR-кодов вместо того, чтобы привязывать реализацию ICarrierEuiccProvisioningService приложения оператора.
    2. Если приложение оператора связи завершает работу со сбоем или отвечает кодом RESULT_CANCELED (это код ответа по умолчанию), LPA отменяет активацию eSIM.
    3. Если приложение оператора возвращает что-то другое, кроме RESULT_OK или RESULT_CANCELED, LPA обрабатывает это как ошибку.

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

Запуск процесса активации LPA в приложении оператора

В Android 11 и более поздних версиях приложения операторов могут использовать API eUICC, чтобы запускать интерфейс пользователя для активации eSIM. Этот метод показывает интерфейс активации eSIM в LPA, чтобы активировать профиль eSIM. После этого LPA отправляет широковещательное сообщение, когда активация профиля eSIM завершается.

  1. В LPA должна быть объявлена Activity, включающая фильтр интентов с действием android.service.euicc.action.START_EUICC_ACTIVATION. Если на устройстве есть несколько реализаций, приоритет фильтра интентов должен быть ненулевым. Пример:

    <application>
      ...
    <activity
        android:name=".CarrierAppInitActivity"
        android:exported="true">
    
        <intent-filter android:priority="100">
            <action android:name="android.service.euicc.action.START_EUICC_ACTIVATION" />
        </intent-filter>
    </activity>
      ...
    </application>
    
  2. Приложение оператора связи выполняет свою работу, используя собственный интерфейс. Например, войти в аккаунт пользователя или отправить HTTP-запросы в серверную часть оператора.

  3. На этом этапе приложение оператора должно быть готово предоставить код активации с помощью реализации ICarrierEuiccProvisioningService. Приложение оператора запускает LPA, вызывая startActivityForResult(Intent, int) с действием android.telephony.euicc.action.START_EUICC_ACTIVATION. LPA также проверяет логический дополнительный параметр android.telephony.euicc.extra.USE_QR_SCANNER. Если значение равно true, LPA запускает сканер QR-кодов, чтобы пользователь мог отсканировать QR-код профиля.

  4. На стороне LPA он подключается к реализации ICarrierEuiccProvisioningService приложения оператора, чтобы получить код активации и скачать соответствующий профиль. Во время скачивания LPA показывает все необходимые элементы интерфейса, например экран загрузки.

  5. После завершения процесса активации LPA отправляет в приложение оператора код результата, который обрабатывается в onActivityResult(int, int, Intent).

    1. Если LPA удается скачать новый профиль eSIM, он отвечает кодом RESULT_OK.
    2. Если пользователь отменит активацию профиля eSIM в LPA, будет получен ответ RESULT_CANCELED.
    3. Если LPA отвечает чем-то иным, кроме RESULT_OK или RESULT_CANCELED, приложение оператора связи считает это ошибкой.

    В целях безопасности LPA не принимает код активации напрямую в предоставленном намерении. Это гарантирует, что вызывающие абоненты, не относящиеся к LPA, не смогут получить код активации из приложения оператора.

Поддержка нескольких eSIM

На устройствах с Android 10 и более поздних версий класс EuiccManager поддерживает устройства с несколькими eSIM. Устройства с одной eSIM, которые обновляются до Android 10, не требуют изменений в реализации LPA, поскольку платформа автоматически связывает экземпляр EuiccManager с eUICC по умолчанию. eUICC по умолчанию определяется платформой для устройств с HAL радиомодуля версии 1.2 или выше и LPA для устройств с HAL радиомодуля версии ниже 1.2.

Требования

Чтобы устройство поддерживало несколько eSIM, в нем должно быть больше одного чипа eUICC. Это может быть встроенный чип или слот для физической SIM-карты, в который можно вставить съемный чип eUICC.

Для поддержки нескольких eSIM требуется Radio HAL версии 1.2 или более поздней. Рекомендуется использовать Radio HAL версии 1.4 и RadioConfig HAL версии 1.2.

Реализация

Чтобы поддерживать несколько eSIM (в том числе съемные eUICC или программируемые SIM-карты), LPA должен реализовать EuiccService, который получает идентификатор слота, соответствующий идентификатору карты, предоставленному вызывающим абонентом.

Ресурс non_removable_euicc_slots, указанный в arrays.xml, представляет собой массив целых чисел, которые являются идентификаторами слотов встроенных eUICC устройства. Этот ресурс необходимо указать, чтобы платформа могла определить, является ли вставленная карта eUICC съемной.

Приложение оператора для устройства с несколькими eSIM

При создании приложения оператора для устройства с несколькими eSIM используйте метод createForCardId в EuiccManager, чтобы создать объект EuiccManager, привязанный к идентификатору определенной карты. Идентификатор карты – это целое число, которое однозначно идентифицирует UICC или eUICC на устройстве.

Чтобы получить идентификатор карты для eUICC по умолчанию, используйте метод getCardIdForDefaultEuicc в TelephonyManager. Этот метод возвращает значение UNSUPPORTED_CARD_ID, если версия HAL радиомодуля ниже 1.2, и значение UNINITIALIZED_CARD_ID, если устройство не прочитало eUICC.

Идентификаторы карт также можно получить из getUiccCardsInfo и getUiccSlotsInfo (системный API) в TelephonyManager, а также из getCardId в SubscriptionInfo.

Когда объект EuiccManager создан с определенным идентификатором карты, все операции направляются на eUICC с этим идентификатором. Если eUICC становится недоступной (например, когда она отключена или удалена), EuiccManager перестает работать.

Чтобы создать приложение оператора, можно использовать приведенные ниже образцы кода.

Пример 1. Получение активной подписки и создание экземпляра EuiccManager

// Get the active subscription and instantiate an EuiccManager for the eUICC which holds
// that subscription
SubscriptionManager subMan = (SubscriptionManager)
        mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
int cardId = subMan.getActiveSubscriptionInfo().getCardId();
EuiccManager euiccMan = (EuiccManager) mContext.getSystemService(Context.EUICC_SERVICE)
            .createForCardId(cardId);

Пример 2. Перебор UICC и создание экземпляра EuiccManager для съемной eUICC

// On a device with a built-in eUICC and a removable eUICC, iterate through the UICC cards
// to instantiate an EuiccManager associated with a removable eUICC
TelephonyManager telMan = (TelephonyManager)
        mContext.getSystemService(Context.TELEPHONY_SERVICE);
List<UiccCardInfo> infos = telMan.getUiccCardsInfo();
int removableCardId = -1; // valid cardIds are 0 or greater
for (UiccCardInfo info : infos) {
    if (info.isRemovable()) {
        removableCardId = info.getCardId();
        break;
    }
}
if (removableCardId != -1) {
    EuiccManager euiccMan = (EuiccManager) mContext.getSystemService(Context.EUICC_SERVICE)
            .createForCardId(removableCardId);
}

Проверка

В AOSP нет реализации LPA, и не ожидается, что LPA будет доступен во всех сборках Android (не все телефоны поддерживают eSIM). По этой причине сквозных тестов CTS не существует. Однако в AOSP доступны базовые тестовые случаи, которые позволяют убедиться, что открытые API eUICC действительны в сборках Android.

Убедитесь, что сборки проходят следующие тесты CTS (для общедоступных API): /platform/cts/tests/tests/telephony/current/src/android/telephony/euicc/cts.

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

Если вы создаете собственную целевую страницу, вам следует провести гораздо более тщательное тестирование. Чтобы устранить проблемы и обеспечить совместимость LPA в архитектуре RSP, обратитесь к поставщику модема, чипа eUICC или ОС eSIM, поставщикам SM-DP+ и операторам связи. Без ручного тестирования не обойтись. Чтобы обеспечить максимальное покрытие тестирования, следуйте плану тестирования GSMA SGP.23 RSP.