实现 eSIM

嵌入式 SIM (eSIM 或 eUICC) 技术允许移动用户下载运营商配置文件并激活运营商服务,而无需使用实体 SIM 卡。它是由 GSMA 推动的全球规范,支持任何移动设备的远程 SIM 配置 (RSP)。从 Android 9 开始,Android 框架提供标准 API 来访问 eSIM 并管理 eSIM 上的订阅配置文件。这些eUICC API 使第三方能够在支持 eSIM 的 Android 设备上开发自己的运营商应用和本地配置文件助理 (LPA)。

LPA 是一个独立的系统应用,应包含在 Android 构建映像中。eSIM 上配置文件的管理通常由 LPA 完成,因为它充当 SM-DP+(远程服务,用于准备、存储和向设备交付配置文件包)和 eUICC 芯片之间的桥梁。LPA APK 可以选择性地包含一个 UI 组件,称为 LPA UI 或 LUI,为最终用户提供一个集中位置来管理所有嵌入式订阅配置文件。Android 框架会自动发现并连接到最佳可用 LPA,并将所有 eUICC 操作路由到 LPA 实例。

Simplified Remote SIM Provisioning (RSP) architecture

图 1. 简化的 RSP 架构

有兴趣创建运营商应用的移动网络运营商应查看 EuiccManager 中的 API,该 API 提供高级配置文件管理操作,例如 downloadSubscription()switchToSubscription()deleteSubscription()

如果您是设备 OEM,并且有兴趣创建自己的 LPA 系统应用,则必须扩展 EuiccService,以便 Android 框架连接到您的 LPA 服务。此外,您应使用 EuiccCardManager 中的 API,这些 API 提供基于 GSMA RSP v2.0 的 ES10x 功能。这些功能用于向 eUICC 芯片发出命令,例如 prepareDownload()loadBoundProfilePackage()retrieveNotificationList()resetMemory()

EuiccManager 中的 API 需要正确实现的 LPA 应用才能运行,并且 EuiccCardManager API 的调用方必须是 LPA。Android 框架会强制执行此操作。

运行 Android 10 或更高版本的设备可以支持具有多个 eSIM 的设备。如需了解详情,请参阅支持多个 eSIM

制作运营商应用

Android 9 中的 eUICC API 使移动网络运营商可以创建运营商品牌应用来直接管理其配置文件。这包括下载和删除运营商拥有的订阅配置文件,以及切换到运营商拥有的配置文件。

EuiccManager

EuiccManager 是应用与 LPA 交互的主要入口点。这包括运营商应用,这些应用可以下载、删除和切换到运营商拥有的订阅。 这也包括 LUI 系统应用,该应用为管理所有嵌入式订阅提供了一个中心位置/UI,并且可以是一个与提供 EuiccService 的应用不同的应用。

要使用公共 API,运营商应用必须首先通过 Context#getSystemService 获取 EuiccManager 的实例

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

在执行任何 eSIM 操作之前,您应该检查设备是否支持 eSIM。EuiccManager#isEnabled() 通常在定义了 android.hardware.telephony.euicc 功能并且存在 LPA 软件包时返回 true

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

要获取有关 eUICC 硬件和 eSIM 操作系统版本的信息

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

许多 API,例如 downloadSubscription()switchToSubscription(),使用 PendingIntent 回调,因为它们可能需要几秒甚至几分钟才能完成。PendingIntentEuiccManager#EMBEDDED_SUBSCRIPTION_RESULT_ 空间中的结果代码一起发送,该空间提供框架定义的错误代码,以及从 LPA 传播的任意详细结果代码 EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,允许运营商应用进行跟踪以用于日志记录/调试目的。PendingIntent 回调必须是 BroadcastReceiver

要下载给定的可下载订阅(从激活码或二维码创建)

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

要切换到给定订阅 ID 的订阅

// 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 包含内部 extra,以便当调用者将其传递给 EuiccManager#startResolutionActivity 时,可以通过 LUI 请求解决。再次以确认码为例,EuiccManager#startResolutionActivity 触发一个 LUI 屏幕,允许用户输入确认码;输入代码后,下载操作将恢复。这种方法为运营商应用提供了对何时显示 UI 的完全控制权,但也为 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) 中的证书,并授予由这些证书签名的应用调用 EuiccManager API 的权限。高级流程如下所述

  1. 运营商签署运营商应用 APK;apksigner 工具将公钥证书附加到 APK。
  2. 运营商/SM-DP+ 准备配置文件及其元数据,其中包括包含以下内容的 ARF

    1. 运营商应用公钥证书的签名(SHA-1 或 SHA-256)(必需)
    2. 运营商应用的软件包名称(强烈建议)
  3. 运营商应用尝试使用 EuiccManager API 执行 eUICC 操作。

  4. Android 平台验证调用者应用的证书的 SHA-1 或 SHA-256 哈希是否与从目标配置文件的 ARF 获取的证书的签名匹配。如果运营商应用的软件包名称包含在 ARF 中,则它还必须与调用者应用的软件包名称匹配。

  5. 在验证签名和软件包名称(如果包含)后,将授予调用者应用对目标配置文件的运营商权限。

由于配置文件元数据可以在配置文件本身之外获得(以便 LPA 可以在下载配置文件之前从 SM-DP+ 检索配置文件元数据,或者在停用配置文件时从 ISD-R 检索配置文件元数据),因此它应包含与配置文件中相同的运营商权限规则。

eUICC 操作系统和 SM-DP+ 必须支持配置文件元数据中的专有标记 BF76。标记内容应与 UICC 运营商权限 中定义的访问规则小程序 (ARA) 返回的运营商权限规则相同

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),该助理必须与 Android Euicc API 挂钩。以下部分简要概述了如何制作 LPA 应用并将其与 Android 系统集成。

硬件/调制解调器要求

eUICC 芯片上的 LPA 和 eSIM 操作系统必须至少支持 GSMA RSP(远程 SIM 配置)v2.0 或 v2.2。您还应计划使用具有匹配 RSP 版本的 SM-DP+ 和 SM-DS 服务器。有关详细的 RSP 架构,请参阅 GSMA SGP.21 RSP 架构规范

此外,为了与 Android 9 中的 eUICC API 集成,设备调制解调器应发送终端功能,其中包含编码的 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 必须实现 IRadioSim.getAllowedCarriers()HAL API。

    • 供应商 RIL/调制解调器应填充锁定状态和设备锁定到的运营商的 carrierId,作为 IRadioSimResponse.getAllowedCarriersResponse()HAL API 的一部分。

调制解调器应将启用了默认启动配置文件的 eSIM 识别为有效的 SIM 卡,并保持 SIM 卡电源开启。

对于运行 Android 10 的设备,必须定义不可移除的 eUICC 插槽 ID 数组。例如,请参阅 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 UI 或 LUI。

要实现 LPA 后端,您必须扩展 EuiccService 并在您的清单文件中声明此服务。该服务必须需要 android.permission.BIND_EUICC_SERVICE 系统权限,以确保只有系统可以绑定到它。该服务还必须包含一个 intent 过滤器,其中包含 android.service.euicc.EuiccService 操作。如果设备上存在多个实现,则应将 intent 过滤器的优先级设置为非零值。例如

<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 并根据需要与其交互以支持 Android eUICC API。PackageManager 查询所有具有 android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS 权限的应用,该权限为 android.service.euicc.EuiccService 操作指定服务。选择优先级最高的服务。如果未找到服务,则禁用 LPA 支持。

要实现 LUI,您必须为以下操作提供一个 activity

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

与服务一样,每个 activity 都必须需要 android.permission.BIND_EUICC_SERVICE 系统权限。每个 activity 都应具有一个 intent 过滤器,其中包含适当的操作、android.service.euicc.category.EUICC_UI 类别和非零优先级。与选择 EuiccService 的实现类似,使用类似的逻辑来选择这些 activity 的实现。例如

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

这意味着实现这些屏幕的 UI 可以来自与实现 EuiccService 的 APK 不同的 APK。是使用单个 APK 还是多个 APK(例如,一个实现 EuiccService,另一个提供 LUI activity)是一个设计选择。

EuiccCardManager

EuiccCardManager 是用于与 eSIM 芯片通信的接口。它提供 ES10 功能(如 GSMA RSP 规范中所述)并处理低级 APDU 请求/响应命令以及 ASN.1 解析。EuiccCardManager 是系统 API,只能由具有系统特权的应用调用。

Carrier apps, LPA, and Euicc APIs

图 2. 运营商应用和 LPA 都使用 Euicc API

通过 EuiccCardManager 的配置文件操作 API 要求调用者是 LPA。这是由 Android 框架强制执行的。这意味着调用者必须扩展 EuiccService 并在您的清单文件中声明,如前面的部分所述。

EuiccManager 类似,要使用 EuiccCardManager API,您的 LPA 必须首先通过 Context#getSystemService 获取 EuiccCardManager 的实例

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 通过 AIDL 接口绑定到 EuiccCardController(在手机进程中运行),并且每个 EuiccCardManager 方法都通过不同的专用 AIDL 接口接收来自手机进程的回调。使用 EuiccCardManager API 时,调用者 (LPA) 必须提供一个 Executor 对象,回调将通过该对象调用。此 Executor 对象可以在单线程或您选择的线程池上运行。

大多数 EuiccCardManager API 都具有相同的使用模式。例如,要将绑定的配置文件软件包加载到 eUICC 上

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

要切换到具有给定 ICCID 的不同配置文件

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

要从 eUICC 芯片获取默认 SM-DP+ 地址

...
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 可以控制整个 UI 流程,这意味着不显示任何运营商应用 UI。此方法绕过了 BF76 标记检查,并且网络运营商无需实现整个 eSIM 激活 UI 流程,包括下载 eSIM 配置文件和错误处理。

定义运营商 eUICC 配置服务

LPA 和运营商应用通过两个 AIDL 接口进行通信:ICarrierEuiccProvisioningServiceIGetActivationCodeCallback。运营商应用必须实现 ICarrierEuiccProvisioningService 接口,并在其 清单声明中公开它。LPA 必须绑定到 ICarrierEuiccProvisioningService 并实现 IGetActivationCodeCallback。有关如何实现和公开 AIDL 接口的更多信息,请参阅 定义和 AIDL 接口

要定义 AIDL 接口,请为 LPA 和运营商应用创建以下 AIDL 文件。

  • 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.aidlIGetActivationCodeCallback.aidl 都复制到您的项目中,并实现 ServiceConnection

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

在绑定到运营商应用的 ICarrierEuiccProvisioningService 实现后,LPA 调用 getActivationCodegetActivationCodeForEid,通过传递 IGetActivationCodeCallback stub 类的实现,从运营商应用获取激活码。

getActivationCodegetActivationCodeForEid 之间的区别在于,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.aidlIGetActivationCodeCallback.aidl 都复制到您的项目中,并在 AndroidManifest.xml 文件中声明 ICarrierEuiccProvisioningService 服务。该服务必须需要 android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS 系统权限,以确保只有 LPA(一个具有系统特权的应用)可以绑定到它。该服务还必须包含一个 intent 过滤器,其中包含 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 类并实现 getActivationCodegetActivationCodeForEid 方法。然后,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 激活流程中启动运营商应用 UI

在运行 Android 11 及更高版本的设备上,LPA 可以启动运营商应用的 UI。这很有用,因为运营商应用可能需要在向 LPA 提供激活码之前从用户那里获取其他信息。例如,运营商可能需要用户登录以激活其电话号码或执行其他携号转网服务。

这是在 LPA 中启动运营商应用 UI 的过程

  1. LPA 通过向包含该操作的运营商应用软件包发送 android.service.euicc.action.START_CARRIER_ACTIVATION intent 来启动运营商应用的激活流程。(运营商应用接收器必须在清单声明中使用 android:permission="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" 进行保护,以避免接收来自非 LPA 应用的 intent。)

    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. 运营商应用使用自己的 UI 执行其工作。例如,登录用户或向运营商的后端发送 HTTP 请求。

  3. 运营商应用通过调用 setResult(int, Intent)finish() 来响应 LPA。

    1. 如果运营商应用使用 RESULT_OK 进行响应,则 LPA 继续激活流程。如果运营商应用确定用户应扫描二维码,而不是让 LPA 绑定运营商应用的服务,则运营商应用使用 setResult(int, Intent) 以及 RESULT_OK 和包含布尔 extra android.telephony.euicc.extra.USE_QR_SCANNER 设置为 trueIntent 实例来响应 LPA。然后,LPA 检查 extra 并启动 QR 扫描器,而不是绑定运营商应用的 ICarrierEuiccProvisioningService 实现。
    2. 如果运营商应用崩溃或使用 RESULT_CANCELED 进行响应(这是默认响应代码),则 LPA 取消 eSIM 激活流程。
    3. 如果运营商应用使用 RESULT_OKRESULT_CANCELED 以外的其他内容进行响应,则 LPA 将其视为错误。

    出于安全原因,LPA 不应直接接受在结果 intent 中提供的激活码,以确保非 LPA 调用者无法从运营商应用获取激活码。

在运营商应用中启动 LPA 激活流程

从 Android 11 开始,运营商应用可以使用 eUICC API 来启动 LUI 以进行 eSIM 激活。此方法显示 LPA 的 eSIM 激活流程 UI 以激活 eSIM 配置文件。然后,LPA 在 eSIM 配置文件激活完成时发送广播。

  1. LPA 必须声明一个 activity,其中包括一个 intent 过滤器,其中包含 android.service.euicc.action.START_EUICC_ACTIVATION 操作。如果设备上存在多个实现,则应将 intent 过滤器的优先级设置为非零值。例如

    <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. 运营商应用使用自己的 UI 执行其工作。例如,登录用户或向运营商的后端发送 HTTP 请求。

  3. 此时,运营商应用必须准备好通过其 ICarrierEuiccProvisioningService 实现提供激活码。运营商应用通过使用 android.telephony.euicc.action.START_EUICC_ACTIVATION 操作调用 startActivityForResult(Intent, int) 来启动 LPA。LPA 还检查布尔 extra android.telephony.euicc.extra.USE_QR_SCANNER。如果值为 true,则 LPA 启动 QR 扫描器,以使用户扫描配置文件 QR 码。

  4. 在 LPA 端,LPA 绑定到运营商应用的 ICarrierEuiccProvisioningService 实现,以获取激活码并下载相应的配置文件。LPA 在下载期间显示所有必要的 UI 元素,例如加载屏幕。

  5. 当 LPA 激活流程完成时,LPA 使用结果代码响应运营商应用,运营商应用在 onActivityResult(int, int, Intent) 中处理该结果代码。

    1. 如果 LPA 成功下载新的 eSIM 配置文件,则它使用 RESULT_OK 进行响应。
    2. 如果用户在 LPA 中取消 eSIM 配置文件激活,则它使用 RESULT_CANCELED 进行响应。
    3. 如果 LPA 使用 RESULT_OKRESULT_CANCELED 以外的其他内容进行响应,则运营商应用将其视为错误。

    出于安全原因,LPA 直接接受在提供的 intent 中提供的激活码,以确保非 LPA 调用者无法从运营商应用获取激活码。

支持多个 eSIM

对于运行 Android 10 或更高版本的设备,EuiccManager 类支持具有多个 eSIM 的设备。对于升级到 Android 10 的单个 eSIM 设备,不需要对 LPA 实现进行任何修改,因为平台会自动将 EuiccManager 实例与默认 eUICC 关联。默认 eUICC 由平台为无线电 HAL 版本 1.2 或更高的设备确定,并由 LPA 为无线电 HAL 版本低于 1.2 的设备确定。

要求

要支持多个 eSIM,设备必须具有多个 eUICC,这可以是内置 eUICC 或可以插入可移除 eUICC 的物理 SIM 卡插槽。

需要无线电 HAL 版本 1.2 或更高版本才能支持多个 eSIM。建议使用无线电 HAL 版本 1.4 和 RadioConfig HAL 版本 1.2。

实现

要支持多个 eSIM(包括可移除 eUICC 或可编程 SIM 卡),LPA 必须实现 EuiccService,它接收与调用者提供的卡 ID 对应的插槽 ID。

non_removable_euicc_slots 资源在 arrays.xml 中指定,它是一个整数数组,表示设备的内置 eUICC 的插槽 ID。您必须指定此资源,以允许平台确定插入的 eUICC 是否可移除。

具有多个 eSIM 的设备的运营商应用

为具有多个 eSIM 的设备制作运营商应用时,请使用 EuiccManager 中的 createForCardId 方法来创建绑定到给定卡 ID 的 EuiccManager 对象。卡 ID 是一个整数值,唯一标识设备上的 UICC 或 eUICC。

要获取设备默认 eUICC 的卡 ID,请使用 TelephonyManager 中的 getCardIdForDefaultEuicc 方法。如果无线电 HAL 版本低于 1.2,则此方法返回 UNSUPPORTED_CARD_ID;如果设备尚未读取 eUICC,则返回 UNINITIALIZED_CARD_ID

您还可以从 TelephonyManager 中的 getUiccCardsInfogetUiccSlotsInfo(系统 API)以及 SubscriptionInfo 中的 getCardId 获取卡 ID。

EuiccManager 对象已使用特定卡 ID 实例化时,所有操作都将定向到具有该卡 ID 的 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 并为可移除 eUICC 实例化 EuiccManager

// 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 实现,并且您不应期望所有 Android 版本都提供 LPA(并非每部手机都支持 eSIM)。因此,没有端到端的 CTS 测试用例。但是,AOSP 中提供了基本测试用例,以确保公开的 eUICC API 在 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 测试计划