嵌入式 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 实例。
图 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
回调,因为它们可能需要几秒甚至几分钟才能完成。PendingIntent
随 EuiccManager#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 的权限。高级流程如下所述
- 运营商签署运营商应用 APK;apksigner 工具将公钥证书附加到 APK。
运营商/SM-DP+ 准备配置文件及其元数据,其中包括包含以下内容的 ARF
- 运营商应用公钥证书的签名(SHA-1 或 SHA-256)(必需)
- 运营商应用的软件包名称(强烈建议)
运营商应用尝试使用
EuiccManager
API 执行 eUICC 操作。Android 平台验证调用者应用的证书的 SHA-1 或 SHA-256 哈希是否与从目标配置文件的 ARF 获取的证书的签名匹配。如果运营商应用的软件包名称包含在 ARF 中,则它还必须与调用者应用的软件包名称匹配。
在验证签名和软件包名称(如果包含)后,将授予调用者应用对目标配置文件的运营商权限。
由于配置文件元数据可以在配置文件本身之外获得(以便 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,只能由具有系统特权的应用调用。
图 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 接口进行通信:ICarrierEuiccProvisioningService
和 IGetActivationCodeCallback
。运营商应用必须实现 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.aidl
和 IGetActivationCodeCallback.aidl
都复制到您的项目中,并实现 ServiceConnection
。
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
mCarrierProvisioningService = ICarrierEuiccProvisioningService.Stub.asInterface(iBinder);
}
在绑定到运营商应用的 ICarrierEuiccProvisioningService
实现后,LPA 调用 getActivationCode
或 getActivationCodeForEid
,通过传递 IGetActivationCodeCallback
stub 类的实现,从运营商应用获取激活码。
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
都复制到您的项目中,并在 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
类并实现 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 激活流程中启动运营商应用 UI
在运行 Android 11 及更高版本的设备上,LPA 可以启动运营商应用的 UI。这很有用,因为运营商应用可能需要在向 LPA 提供激活码之前从用户那里获取其他信息。例如,运营商可能需要用户登录以激活其电话号码或执行其他携号转网服务。
这是在 LPA 中启动运营商应用 UI 的过程
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);
运营商应用使用自己的 UI 执行其工作。例如,登录用户或向运营商的后端发送 HTTP 请求。
运营商应用通过调用
setResult(int, Intent)
和finish()
来响应 LPA。- 如果运营商应用使用
RESULT_OK
进行响应,则 LPA 继续激活流程。如果运营商应用确定用户应扫描二维码,而不是让 LPA 绑定运营商应用的服务,则运营商应用使用setResult(int, Intent)
以及RESULT_OK
和包含布尔 extraandroid.telephony.euicc.extra.USE_QR_SCANNER
设置为true
的Intent
实例来响应 LPA。然后,LPA 检查 extra 并启动 QR 扫描器,而不是绑定运营商应用的ICarrierEuiccProvisioningService
实现。 - 如果运营商应用崩溃或使用
RESULT_CANCELED
进行响应(这是默认响应代码),则 LPA 取消 eSIM 激活流程。 - 如果运营商应用使用
RESULT_OK
或RESULT_CANCELED
以外的其他内容进行响应,则 LPA 将其视为错误。
出于安全原因,LPA 不应直接接受在结果 intent 中提供的激活码,以确保非 LPA 调用者无法从运营商应用获取激活码。
- 如果运营商应用使用
在运营商应用中启动 LPA 激活流程
从 Android 11 开始,运营商应用可以使用 eUICC API 来启动 LUI 以进行 eSIM 激活。此方法显示 LPA 的 eSIM 激活流程 UI 以激活 eSIM 配置文件。然后,LPA 在 eSIM 配置文件激活完成时发送广播。
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>
运营商应用使用自己的 UI 执行其工作。例如,登录用户或向运营商的后端发送 HTTP 请求。
此时,运营商应用必须准备好通过其
ICarrierEuiccProvisioningService
实现提供激活码。运营商应用通过使用android.telephony.euicc.action.START_EUICC_ACTIVATION
操作调用startActivityForResult(Intent, int)
来启动 LPA。LPA 还检查布尔 extraandroid.telephony.euicc.extra.USE_QR_SCANNER
。如果值为true
,则 LPA 启动 QR 扫描器,以使用户扫描配置文件 QR 码。在 LPA 端,LPA 绑定到运营商应用的
ICarrierEuiccProvisioningService
实现,以获取激活码并下载相应的配置文件。LPA 在下载期间显示所有必要的 UI 元素,例如加载屏幕。当 LPA 激活流程完成时,LPA 使用结果代码响应运营商应用,运营商应用在
onActivityResult(int, int, Intent)
中处理该结果代码。- 如果 LPA 成功下载新的 eSIM 配置文件,则它使用
RESULT_OK
进行响应。 - 如果用户在 LPA 中取消 eSIM 配置文件激活,则它使用
RESULT_CANCELED
进行响应。 - 如果 LPA 使用
RESULT_OK
或RESULT_CANCELED
以外的其他内容进行响应,则运营商应用将其视为错误。
出于安全原因,LPA 不直接接受在提供的 intent 中提供的激活码,以确保非 LPA 调用者无法从运营商应用获取激活码。
- 如果 LPA 成功下载新的 eSIM 配置文件,则它使用
支持多个 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
中的 getUiccCardsInfo
和 getUiccSlotsInfo
(系统 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 测试计划。