實施 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 可以選擇包含一個 UI 元件,稱為 LPA UI 或 LUI,為最終用戶提供管理所有嵌入式訂閱設定檔的中心位置。 Android 框架自動發現並連接到最佳的可用 LPA,並透過 LPA 實例路由所有 eUICC 操作。

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

圖 1.簡化的 RSP 架構

有興趣建立營運商應用程式的行動網路營運商應該查看EuiccManager中的 API,它提供進階設定檔管理操作,例如downloadSubscription()switchToSubscription()deleteSubscription()

如果您是有興趣建立自己的 LPA 系統應用程式的裝置 OEM,則必須為 Android 框架擴展EuiccService以連接到您的 LPA 服務。另外,您應該使用EuiccCardManager中的 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

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

若要下載給定的可下載訂閱(透過啟動碼或 QR 碼建立):

// 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包含內部額外內容,因此當呼叫者將其傳遞給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 作業系統和 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/數據機應填充設備鎖定的運營商的鎖定狀態和運營商 ID,作為 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 支援的數據機要求

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。查詢PackageManager以尋找具有android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS權限的所有應用,該權限為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 還是多個 APK(例如,一個實作EuiccService APK 和一個提供 LUI 活動的 APK)是一種設計選擇。

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操作的 Intent 過濾器。

  • 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 應用程式的意圖。)

    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 將繼續啟動流程。如果運營商應用程式確定使用者應該掃描 QR 碼而不是讓 LPA 綁定運營商應用程式的服務,則運營商應用程式將使用setResult(int, Intent)以及RESULT_OK和包含布林值額外 android.services 的Intent實例來回應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 啟動用於 eSIM 啟動的 LUI。此方法顯示 LPA 的 eSIM 啟動流程 UI 以啟動 eSIM 設定檔。當 eSIM 設定檔啟動完成時,LPA 會發送廣播。

  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 也會檢查布林值 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直接在提供的意圖中接受啟動碼,以確保非 LPA 呼叫者無法從運營商應用程式取得啟動碼。

支援多個 eSIM

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

要求

為了支援多個 eSIM,設備必須具有多個 eUICC,這些 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資源是一個整數數組,表示裝置內建 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。

當使用特定卡 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 並實例化可移動 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 OS 供應商、SM-DP+ 供應商和營運商合作,解決問題並確保 LPA 在 RSP 架構內的互通性。大量的手動測試是不可避免的。為了獲得最佳測試覆蓋範圍,您應該遵循GSMA SGP.23 RSP 測試計劃