實作 eSIM 卡

嵌入式 SIM 卡 (eSIM 卡或 eUICC) 技術可讓行動使用者不必使用實體 SIM 卡,也能下載電信業者設定檔並啟用電信業者的服務。這是由 GSMA 推動的全球規格,可為任何行動裝置啟用遠端 SIM 卡佈建 (RSP)。自 Android 9 起,Android 架構會提供標準 API,用於存取 eSIM 卡並管理 eSIM 卡上的訂閱設定檔。這些 eUICC API 可讓第三方在支援 eSIM 的 Android 裝置上開發自家的電信業者應用程式和本機設定檔輔助程式 (LPA)。

LPA 是獨立的系統應用程式,應納入 Android 建構映像檔。eSIM 上的設定檔通常由 LPA 管理,因為 LPA 可做為 SM-DP+ (準備、儲存及將設定檔套件傳送至裝置的遠端服務) 和 eUICC 晶片之間的橋樑。LPA APK 可選擇納入稱為 LPA UI 或 LUI 的 UI 元件,為使用者提供集中式位置,用於管理所有嵌入式訂閱設定檔。Android 架構會自動偵測並連線至可用的最佳 LPA,並透過 LPA 例項轉送所有 eUICC 作業。

簡化的遠端 SIM 卡佈建 (RSP) 架構

圖 1. 簡化版 RSP 架構

如果行動網路業者有意建立電信業者應用程式,請參閱 EuiccManager 中的 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 卡。如果已定義 android.hardware.telephony.euicc 功能且存在 LPA 套件,EuiccManager#isEnabled() 通常會傳回 true

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

如要取得 eUICC 硬體和 eSIM OS 版本的相關資訊,請按照下列步驟操作:

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

許多 API (例如 downloadSubscription()switchToSubscription()) 都會使用 PendingIntent 回呼,因為這些 API 可能需要幾秒甚至幾分鐘的時間才能完成。PendingIntent 會與 EuiccManager#EMBEDDED_SUBSCRIPTION_RESULT_ 空間中的結果碼一起傳送,該結果碼會提供架構定義的錯誤代碼,以及從 LPA 以 EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE 形式傳播的任意詳細結果碼,方便電信業者應用程式追蹤記錄/偵錯。PendingIntent 回呼必須為 BroadcastReceiver

如何下載指定的可下載訂閱項目 (透過啟用碼或 QR code 建立):

// 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 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),但必須連結至 Android Euicc API。以下各節將簡要介紹如何製作 LPA 應用程式,並將其與 Android 系統整合。

硬體/模組需求

eUICC 晶片上的 LPA 和 eSIM OS 至少必須支援 GSMA RSP (Remote SIM Provisioning) 2.0 版或 2.2 版。您也應使用相符的 RSP 版本,使用 SM-DP+ 和 SM-DS 伺服器。如需詳細的 RSP 架構,請參閱 GSMA SGP.21 RSP 架構規格

此外,如要整合 Android 9 中的 eUICC API,裝置數據機應傳送終端機功能,並支援編碼後的 eUICC 功能 (本機設定檔管理和設定檔下載)。也需要實作下列方法:

  • IRadio HAL 1.1 版:setSimPower
  • IRadio HAL 1.2 版:getIccCardStatus

  • IRadioConfig HAL 1.0 版:getSimSlotsStatus

  • IRadioConfig AIDL v1.0:getAllowedCarriers

    Google LPA 必須瞭解電信業者鎖定狀態,才能僅允許透過允許的電信業者下載 eSIM 卡或轉移 eSIM 卡。否則使用者可能會下載並轉移 SIM 卡,但之後發現裝置綁定其他電信業者的服務。

    • 供應商或原始設備製造商必須實作 IRadioSim.getAllowedCarriers() HAL API。

    • 供應商 RIL / 數據機應根據 IRadioSimResponse.getAllowedCarriersResponse()HAL API 的一部分,填入裝置鎖定的電信業者鎖定狀態和 CarrierId。

數據機應將已啟用預設啟動設定檔的 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 系統權限,確保只有系統可繫結至該服務。服務也必須包含含有 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 架構中,Android 會判斷哪個 LPA 處於活動狀態,並視需要與該 LPA 互動,以支援 Android eUICC API。凡是具有 android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS 權限的應用程式 (指定 android.service.euicc.EuiccService 動作的服務),都會向 PackageManager 進行查詢。系統會選取優先順序最高的服務。如果找不到服務,系統就會停用 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 可來自不同 APK,而非實作 EuiccService 的 APK。您可以選擇要使用單一 APK 還是多個 APK (例如實作 EuiccService 和提供 LUI 活動的 APK)。

EuiccCardManager

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 提供啟用碼。例如,電信業者可能會要求使用者登入,才能啟用電話號碼或執行其他攜碼轉移服務。

以下是啟動電信業者應用程式 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 就會繼續啟用流程。如果電信業者應用程式判斷使用者應掃描 QR code,而不是讓 LPA 繫結電信業者應用程式服務,則電信業者應用程式會使用 setResult(int, Intent) 回應 LPA,其中 RESULT_OKIntent 例項,其中包含設為 true 的布林額外項目 android.telephony.euicc.extra.USE_QR_SCANNER。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 code。

  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。如果裝置使用單一 eSIM 卡,且要升級至 Android 10,則無須修改 LPA 實作項目,因為平台會自動將 EuiccManager 執行個體與預設 eUICC 建立關聯。如果裝置的無線電 HAL 版本為 1.2 以上,則預設的 eUICC 會由平台決定;如果裝置的無線電 HAL 版本低於 1.2,則會由 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。

EuiccManager 物件已使用特定卡片 ID 例項化時,所有作業都會導向具有該卡片 ID 的 eUICC。如果無法連線至 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+ 供應商和電信業者合作,解決問題並確保 RSP 架構中 LPA 的互通性。進行大量的手動測試是無法避免的為了獲得最佳的測試涵蓋範圍,您應遵循 GSMA SGP.23 RSP 測試計畫