Внедрить eSIM

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

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

Упрощенная архитектура удаленного предоставления SIM-карт (RSP)

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

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

Если вы OEM-производитель устройств, заинтересованный в создании собственного приложения системы 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 следует проверить, поддерживается ли на устройстве 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);

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

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

В некоторых случаях система не может завершить операцию 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 устройства, и каждому приложению оператора должен быть разрешен доступ только к профилям, принадлежащим этому оператору. Например, оператор A не должен иметь возможности загружать, включать или отключать профиль, принадлежащий оператору B.

Чтобы гарантировать, что профиль доступен только его владельцу, 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 OS и SM-DP+ должны поддерживать фирменный тег BF76 в метаданных профиля. Содержимое тега должно быть теми же правилами привилегий оператора, которые возвращаются апплетом правил доступа (ARA), определенным в привилегиях оператора UICC :

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 Android Euicc. В следующих разделах дается краткий обзор создания приложения LPA и его интеграции с системой Android.

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

LPA и eSIM OS на чипе eUICC должны поддерживать как минимум GSMA RSP (Remote SIM Provisioning) v2.0 или v2.2. Вам также следует запланировать использование серверов SM-DP+ и SM-DS с соответствующей версией RSP. Подробную архитектуру RSP см. в спецификации архитектуры GSMA SGP.21 RSP .

Кроме того, для интеграции с 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-карту, а затем обнаружить, что устройство заблокировано на другого оператора.

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

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

Модем должен распознать eSIM с включенным профилем загрузки по умолчанию как допустимую SIM-карту и оставить питание 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 Android eUICC. 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 или нескольких APK (например, одного, реализующего EuiccService , и другого, предоставляющего действия LUI) — это выбор дизайна.

EuiccCardManager

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

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

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

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

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

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

После привязки к реализации ICarrierEuiccProvisioningService приложения оператора LPA вызывает 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 в пакет приложения оператора, содержащий действие. (Приемник приложения оператора должен быть защищен в объявлении манифеста с помощью android:permission="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" , чтобы избежать получения намерений от приложений, не являющихся приложениями LPA.)

    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 для запуска LUI для активации eSIM. Этот метод выводит на экран пользовательский интерфейс потока активации eSIM LPA для активации профиля eSIM. Затем LPA отправляет широковещательное сообщение, когда активация профиля eSIM завершается.

  1. LPA должен объявить активность, включая фильтр намерений с действием 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 LPA привязывается к реализации ICarrierEuiccProvisioningService приложения оператора для получения кода активации и загрузки соответствующего профиля. LPA отображает все необходимые элементы пользовательского интерфейса во время загрузки, такие как экран загрузки.

  5. После завершения процесса активации LPA 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, которая может быть либо встроенной картой 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, вам следует пройти гораздо более тщательное тестирование. Вам следует работать с поставщиком модема, чипа eUICC или ОС eSIM, поставщиками SM-DP+ и операторами, чтобы решить проблемы и обеспечить совместимость вашего LPA с архитектурой RSP. Значительное количество ручного тестирования неизбежно. Для лучшего покрытия тестами вам следует следовать плану тестирования GSMA SGP.23 RSP .