在 Android 9 中,配置文件管理 API(公共 API 和 @SystemApi)通过 EuiccManager
类提供。eUICC 通信 API(仅限 @SystemApi)通过 EuiccCardManager
类提供。
eUICC 简介
运营商可以构建使用 EuiccManager 管理配置文件的运营商应用(如图 1 所示)。运营商应用不需要是系统应用,但需要拥有 eUICC 配置文件授予的运营商权限。LPA 应用(LUI 和 LPA 后端)需要是系统应用(即包含在系统映像中)才能调用 @SystemApi。
图 1. 安装了运营商应用和 OEM LPA 的 Android 手机
除了实现用于调用 EuiccCardManager
以及与 eUICC 通信的逻辑之外,LPA 应用还必须实现以下各项:
- SM-DP+ 客户端,用于与 SM-DP+ 服务器通信以进行身份验证和下载配置文件
- [可选] SM-DS,用于获得更多潜在的可下载配置文件
- 通知处理逻辑,用于向服务器发送通知以便更新配置文件状态
- [可选] 插槽管理逻辑,包括在 eSIM 卡和 pSIM 卡逻辑之间切换。如果手机只有一个 eSIM 卡芯片,则此项为可选项。
- eSIM 卡 OTA
虽然一部 Android 手机上可以有多个 LPA 应用,但只能根据每个应用的 AndroidManifest.xml
文件中定义的优先级来选择一个 LPA 作为实际运行的 LPA。
使用 EuiccManager
LPA API 通过 EuiccManager
(在 android.telephony.euicc
软件包下)公开提供。运营商应用可以获得 EuiccManager
的实例,并调用 EuiccManager
中的方法,以获取 eUICC 信息并将订阅(在 GSMA RSP 文档中称为“配置文件”)作为 SubscriptionInfo 实例进行管理。
如需调用公共 API(包括下载、切换和删除订阅操作),运营商应用必须具有所需的权限。运营商权限由移动运营商添加到配置文件元数据中。eUICC API 会相应地强制执行运营商权限规则。
Android 平台不处理配置文件政策规则。如果政策规则是在配置文件元数据中声明的,则 LPA 可以选择如何处理配置文件下载和安装过程。例如,第三方 OEM LPA 可以使用特殊错误代码处理政策规则(错误代码会从 OEM LPA 传递到平台,然后平台会再将其传递到 OEM LUI)。
如需了解多个已启用的配置文件 API,请参阅多个已启用的配置文件。
API
您可以在 EuiccManager
参考文档和 EuiccManager.java
中找到以下 API。
获取实例(公共)
通过 Context#getSystemService
获取 EuiccManager
的实例。
如需了解详情,请参阅 getSystemService
。
EuiccManager mgr = (EuiccManager) context.getSystemService(Context.EUICC_SERVICE);
检查启用情况(公共)
检查是否已启用嵌入式订阅。应在访问 LPA API 之前完成这项检查。如需了解详情,请参阅 isEnabled
。
boolean isEnabled = mgr.isEnabled();
if (!isEnabled) {
return;
}
获取 EID(公共)
获取用于标识 eUICC 硬件的 EID。如果 eUICC 尚未就绪,EID 可能为 null。调用方必须具备运营商权限或 READ_PRIVILEGED_PHONE_STATE
权限。如需了解详情,请参阅 getEid
。
String eid = mgr.getEid();
if (eid == null) {
// Handle null case.
}
获取 EuiccInfo(公共)
获取有关 eUICC 的信息,其中包含操作系统版本。如需了解详情,请参阅 getEuiccInfo
。
EuiccInfo info = mgr.getEuiccInfo();
String osVer = info.getOsVersion();
下载订阅(公共)
下载指定订阅(在 GSMA RSP 文档中称为“配置文件”)。可以通过激活码创建订阅。例如,可以从二维码中解析激活码。下载订阅是一项异步操作。
调用方必须具有 WRITE_EMBEDDED_SUBSCRIPTIONS
权限或者对目标订阅具备运营商权限。如需了解详情,请参阅 downloadSubscription
。
// Register receiver.
String action = "download_subscription";
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),
"example.broadcast.permission" /* broadcastPermission*/, null /* handler */);
// Download subscription asynchronously.
DownloadableSubscription sub =
DownloadableSubscription.forActivationCode(code /* encodedActivationCode*/);
Intent intent = new Intent(action);
PendingIntent callbackIntent = PendingIntent.getBroadcast(
getContext(), 0 /* requestCode */, intent, PendingIntent.FLAG_UPDATE_CURRENT);
mgr.downloadSubscription(sub, true /* switchAfterDownload */, callbackIntent);
切换订阅(公共)
切换到(启用)指定的订阅。调用方必须具有 WRITE_EMBEDDED_SUBSCRIPTIONS
权限或者对当前启用的订阅和目标订阅具备运营商权限。如需了解详情,请参阅 switchToSubscription
。
// Register receiver.
String action = "switch_to_subscription";
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),
"example.broadcast.permission" /* broadcastPermission*/, null /* handler */);
// Switch to a subscription asynchronously.
Intent intent = new Intent(action);
PendingIntent callbackIntent = PendingIntent.getBroadcast(
getContext(), 0 /* requestCode */, intent, PendingIntent.FLAG_UPDATE_CURRENT);
mgr.switchToSubscription(1 /* subscriptionId */, callbackIntent);
通过端口切换订阅(公共)
(从 Android 13 开始提供)切换到(启用)具有指定端口索引的给定订阅。调用方必须具有 WRITE_EMBEDDED_SUBSCRIPTIONS
权限或者对当前启用的订阅和目标订阅具备运营商权限。如需了解详情,请参阅 switchToSubscription
。
// Register receiver.
String action = "switch_to_subscription";
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),
"example.broadcast.permission" /* broadcastPermission*/, null /* handler */);
// Switch to a subscription asynchronously.
Intent intent = new Intent(action);
PendingIntent callbackIntent = PendingIntent.getBroadcast(
getContext(), 0 /* requestCode */, intent, PendingIntent.FLAG_UPDATE_CURRENT);
mgr.switchToSubscription(1 /* subscriptionId */, 0 /*portIndex*/, callbackIntent);
SIM 卡端口是否可用(公共)
public boolean isSimPortAvailable(int portIndex)
(从 Android 13 开始提供)返回结果,表示传递的端口索引是否可用。如果某个端口未启用订阅或发起调用的应用对于选定端口上安装的订阅具备运营商特权,则该端口可用。如需了解详情,请参阅 isSimPortAvailable
。
删除订阅(公共)
删除具有某个订阅 ID 的订阅。如果相应订阅当前处于活动状态,应先将其停用。调用方必须具有 WRITE_EMBEDDED_SUBSCRIPTIONS
权限或者对目标订阅具备运营商权限。如需了解详情,请参阅 deleteSubscription
。
// Register receiver.
String action = "delete_subscription";
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),
"example.broadcast.permission" /* broadcastPermission*/,
null /* handler */);
// Delete a subscription asynchronously.
Intent intent = new Intent(action);
PendingIntent callbackIntent = PendingIntent.getBroadcast(
getContext(), 0 /* requestCode */, intent, PendingIntent.FLAG_UPDATE_CURRENT);
mgr.deleteSubscription(1 /* subscriptionId */, callbackIntent);
清空所有订阅(系统 API)
清除设备上的所有订阅。从 Android 11 开始,您应提供一个 EuiccCardManager#ResetOption
枚举值,以指定是清除所有测试订阅、所有实际使用的订阅,还是将这两种订阅均清除。调用方必须具有 WRITE_EMBEDDED_SUBSCRIPTIONS
权限。
// Register receiver.
String action = "delete_subscription";
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),
"example.broadcast.permission" /* broadcastPermission*/,
null /* handler */);
// Erase all operational subscriptions asynchronously.
Intent intent = new Intent(action);
PendingIntent callbackIntent = PendingIntent.getBroadcast(
getContext(), 0 /* requestCode */, intent, PendingIntent.FLAG_UPDATE_CURRENT);
mgr.eraseSubscriptions(
EuiccCardManager.RESET_OPTION_DELETE_OPERATIONAL_PROFILES, callbackIntent);
启动解决方案 activity(公共)
启动一个 Activity 以解决可由用户解决的错误。如果操作返回 EuiccManager#EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR
,则可以调用此方法来提示用户解决相应问题。对于同一项错误,此方法只能调用一次。
...
mgr.startResolutionActivity(getActivity(), 0 /* requestCode */, resultIntent, callbackIntent);
常量
如需查看 EuiccManager
中的 public
常量列表,请参阅常量。