實施 eSIM

嵌入式 SIM(eSIM 或 eUICC)技術允許移動用戶在沒有物理 SIM 卡的情況下下載運營商配置文件並激活運營商的服務。它是由 GSMA 推動的全球規範,支持任何移動設備的遠程 SIM 配置 (RSP)。從 Android 9 開始,Android 框架提供了用於訪問 eSIM 和管理 eSIM 上的訂閱配置文件的標準 API。這些eUICC API使第三方能夠在支持 eSIM 的 Android 設備上開發自己的運營商應用程序和本地配置文件助手 (LPA)。

LPA 是一個獨立的系統應用程序,應包含在 Android 構建映像中。 eSIM 上的配置文件管理通常由 LPA 完成,因為它充當 SM-DP+(為設備準備、存儲和交付配置文件包的遠程服務)和 eUICC 芯片之間的橋樑。 LPA APK 可以選擇包含一個稱為 LPA UI 或 LUI 的 UI 組件,以便為最終用戶提供一個管理所有嵌入式訂閱配置文件的中心位置。 Android 框架會自動發現並連接到最佳可用 LPA,並通過 LPA 實例路由所有 eUICC 操作。

簡化的遠程 SIM 配置 (RSP) 架構

圖 1.簡化的 RSP 架構

有興趣創建運營商應用程序的移動網絡運營商應該查看EuiccManager中的 API,它提供了高級配置文件管理操作,例如downloadSubscription()switchToSubscription()deleteSubscription()

如果您是有興趣創建自己的 LPA 系統應用的設備 OEM,則必須擴展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 使移動網絡運營商能夠創建運營商品牌的應用程序來直接管理他們的個人資料。這包括下載和刪除運營商擁有的訂閱配置文件,以及切換到運營商擁有的配置文件。

Euicc管理器

EuiccManager是應用程序與 LPA 交互的主要入口點。這包括下載、刪除和切換到運營商擁有的訂閱的運營商應用程序。這還包括 LUI 系統應用程序,它提供了一個用於管理所有嵌入式訂閱的中央位置/UI,並且可以是與提供EuiccService的應用程序不同的應用程序。

要使用公共 API,運營商應用必須首先通過Context#getSystemService獲取EuiccManager的實例:

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

在執行任何 eSIM 操作之前,您應該檢查設備是否支持 eSIM。如果定義了android.hardware.telephony.euicc功能並且存在 LPA 包,則EuiccManager#isEnabled()通常會返回true

boolean isEnabled = mgr.isEnabled();
if (!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*/);
                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);
PendingIntent callbackIntent = PendingIntent.getBroadcast(
        getContext(), 0 /* requestCode */, intent,
        PendingIntent.FLAG_UPDATE_CURRENT);
mgr.downloadSubscription(sub, true /* switchAfterDownload */,
        callbackIntent);

要切換到給定訂閱 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);
PendingIntent callbackIntent = PendingIntent.getBroadcast(
        getContext(), 0 /* requestCode */, intent,
        PendingIntent.FLAG_UPDATE_CURRENT);
mgr.switchToSubscription(1 /* subscriptionId */, callbackIntent);

有關EuiccManager API 和代碼示例的完整列表,請參閱eUICC API

可解決的錯誤

在某些情況下,系統無法完成 eSIM 操作,但錯誤可以由用戶解決。例如,如果配置文件元數據指示需要運營商確認碼,則downloadSubscription可能會失敗。或者,如果運營商應用程序對目標配置文件具有運營商權限(即運營商擁有該配置文件),但對當前啟用的配置文件沒有運營商權限,則switchToSubscription可能會失敗,因此需要用戶同意。

對於這些情況,調用者的回調使用EuiccManager#EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR調用。回調Intent包含內部額外內容,因此當調用者將其傳遞給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. Operator/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 OS 和 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 應用程序

您可以實現自己的 LPA,它必須與 Android Euicc API 掛鉤。以下部分簡要概述了製作 LPA 應用程序並將其與 Android 系統集成。

硬件/調製解調器要求

eUICC 芯片上的 LPA 和 eSIM OS 必須至少支持 GSMA RSP (Remote SIM Provisioning) 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

調製解調器應將啟用了默認引導配置文件的 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 支持的調製解調器要求

Euicc服務

LPA 包含兩個獨立的組件(可能都在同一個 APK 中實現):LPA 後端和 LPA UI 或 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 並根據需要與其交互以支持 Android eUICC API。使用android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS權限查詢所有應用程序的PackageManager ,該權限為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>

這意味著實現這些屏幕的 UI 可以來自與實現EuiccService的不同的 APK。是否擁有單個 APK 或多個 APK(例如,一個實現EuiccService和一個提供 LUI 活動)是一種設計選擇。

Euicc卡管理器

EuiccCardManager是與 eSIM 芯片通信的接口。它提供 ES10 功能(如 GSMA RSP 規範中所述)並處理低級 APDU 請求/響應命令以及 ASN.1 解析。 EuiccCardManager是一個系統 API,只能由系統特權應用程序調用。

運營商應用程序、LPA 和 Euicc 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接口進行通信: 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存根類的實現從運營商應用獲取激活碼。

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(系統特權應用)可以綁定到它。該服務還必須包含一個帶有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意圖來啟動運營商應用程序的激活流程。 (運營商應用接收器必須在清單聲明中使用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. 運營商應用程序使用自己的 UI 完成工作。例如,登錄用戶或向運營商的後端發送 HTTP 請求。

  3. 運營商應用通過調用setResult(int, Intent)finish()來響應 LPA。

    1. 如果運營商應用以RESULT_OK響應,LPA 將繼續激活流程。如果運營商應用程序確定用戶應該掃描二維碼而不是讓 LPA 綁定運營商應用程序的服務,則運營商應用程序使用帶有RESULT_OKsetResult(int, Intent)和包含布爾額外android.telephony.euicc.extra.USE_QR_SCANNERIntent實例來響應 LPA。 android.telephony.euicc.extra.USE_QR_SCANNER設置為true 。 LPA 然後檢查額外內容並啟動 QR 掃描儀,而不是綁定運營商應用的ICarrierEuiccProvisioningService實現。
    2. 如果運營商應用程序崩潰或響應RESULT_CANCELED (這是默認響應代碼),LPA 將取消 eSIM 激活流程。
    3. 如果運營商應用程序響應的不是RESULT_OKRESULT_CANCELED ,則 LPA 會將其視為錯誤。

    出於安全原因,LPA不應直接接受結果意圖中提供的激活碼,以確保非 LPA 調用者無法從運營商應用程序獲取激活碼。

在運營商應用程序中啟動 LPA 激活流程

從 Android 11 開始,運營商應用可以使用 eUICC API 啟動 LUI 以激活 eSIM。此方法顯示 LPA 的 eSIM 激活流程 UI 以激活 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. 運營商應用程序使用自己的 UI 完成工作。例如,登錄用戶或向運營商的後端發送 HTTP 請求。

  3. 此時,運營商應用程序必須準備好通過其ICarrierEuiccProvisioningService實現提供激活碼。運營商應用通過使用android.telephony.euicc.action.START_EUICC_ACTIVATION操作調用startActivityForResult(Intent, int)來啟動 LPA。 LPA 還檢查布爾額外的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直接在提供的意圖中接受激活碼,以確保非 LPA 調用者無法從運營商應用程序獲取激活碼。

支持多個 eSIM

對於運行 Android 10 或更高版本的設備, EuiccManager類支持具有多個 eSIM 的設備。升級到 Android 10 的具有單個 eSIM 的設備不需要對 LPA 實現進行任何修改,因為平台會自動將EuiccManager實例與默認 eUICC 關聯。對於具有無線電 HAL 版本 1.2 或更高版本的設備,默認 eUICC 由平台確定,對於無線電 HAL 版本低於 1.2 的設備,由 LPA 確定。

要求

要支持多個 eSIM,設備必須具有多個 eUICC,可以是內置的 eUICC,也可以是可以插入可移動 eUICC 的物理 SIM 插槽。

需要 Radio HAL 1.2 或更高版本才能支持多個 eSIM。建議使用 Radio HAL 1.4 版和 RadioConfig HAL 1.2 版。

執行

為了支持多個 eSIM(包括可移動 eUICC 或可編程 SIM),LPA 必須實現EuiccService ,它接收與調用者提供的卡 ID 對應的插槽 ID。

arrays.xml 中指定的non_removable_euicc_slots資源是一個整數數組,表示設備內置arrays.xml的插槽 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)以及getCardId中的SubscriptionInfo獲取卡 ID。

當使用特定卡 ID 實例化EuiccManager對象時,所有操作都將定向到具有該卡 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 並實例化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 實現,並且您不希望在所有 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 測試計劃