eUICC API

在 Android 9 中,配置文件管理 API(公共 API 和 @SystemApi)通过 EuiccManager 类提供。eUICC 通信 API(仅限 @SystemApi)通过 EuiccCardManager 类提供。

eUICC 简介

运营商可以构建使用 EuiccManager 管理配置文件的运营商应用(如图 1 所示)。运营商应用不需要是系统应用,但需要拥有 eUICC 配置文件授予的运营商权限。LPA 应用(LUI 和 LPA 后端)需要是系统应用(即包含在系统映像中)才能调用 @SystemApi。

安装了运营商应用和 OEM LPA 的 Android 手机

图 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

您可以在 EuiccManager 参考文档EuiccManager.java 中找到以下 API。

获取实例(公共)

通过 Context#getSystemService 获取 EuiccManager 的实例。

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

检查启用情况(公共)

检查是否已启用嵌入式订阅。您应在访问 LPA API 之前完成这项检查。

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

获取 EID(公共)

获取用于标识 eUICC 硬件的 EID。如果 eUICC 尚未就绪,IED 可能为 null。调用程序必须具备运营商权限或 READ_PRIVILEGED_PHONE_STATE 权限。

String eid = mgr.getEid();
if (eid == null) {
  // Handle null case.
}

获取 EuiccInfo(公共)

获取有关 eUICC 的信息,其中包含操作系统版本。

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

下载订阅(公共)

下载指定订阅(在 GSMA RSP 文档中称为“配置文件”)。您可以通过激活码创建订阅。例如,您可以从 QR 码中解析激活码。下载订阅是一项异步操作。

调用程序必须具有 WRITE_EMBEDDED_SUBSCRIPTIONS 权限或针对目标订阅的运营商权限。

// 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 权限或针对当前启用的订阅和目标订阅的运营商权限。

// 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);

删除订阅(公共)

删除具有订阅 ID 的订阅。如果相应订阅当前处于有效状态,则应先予以停用。调用程序必须具有 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 */);

// 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);

启动解决活动(公共)

启动一项活动以修正可由用户修正的错误。如果操作返回 EuiccManager#EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR,则可以调用此方法,以提示用户解决相应问题。对于特定错误,此方法只能调用一次。

...
mgr.startResolutionActivity(getActivity(), 0 /* requestCode */, resultIntent, callbackIntent);

常量

要查看 EuiccManager 中的 public 常量列表,请参阅常量