實作 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 可選擇性地納入 UI 元件 (稱為 LPA UI 或 LUI),為使用者提供集中管理所有內嵌訂閱設定檔的位置。Android 架構會自動探索並連線至最佳 LPA,並透過 LPA 執行個體傳送所有 eUICC 作業。

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

圖 1. 簡化 RSP 架構

有興趣建立電信業者應用程式的行動網路業者,應查看 EuiccManager 中的 API,這些 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 作業系統版本的相關資訊,請按照下列步驟操作:

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. 電信業者/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 (遠端 SIM 卡供應) 2.0 版或 2.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 卡,之後才發現裝置已鎖定其他電信業者。

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

    • 供應商 RIL / 數據機應填入裝置鎖定的電信業者鎖定狀態和 carrierId,做為 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 的數據機需求」。

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 架構會判斷有效的 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 或多個 APK (例如實作 EuiccService 的 APK 和提供 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 可以啟動這項流程,並控制整個 UI 流程,因此不會顯示任何電信業者應用程式 UI。這種做法會略過 BF76 標記檢查,網路業者也不需要實作整個 eSIM 啟用使用者介面流程,包括下載 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 會繼續啟用流程。如果電信業者應用程式判斷使用者應掃描 QR code,而非讓 LPA 繫結電信業者應用程式的服務,電信業者應用程式會使用 setResult(int, Intent) 回應 LPA,其中包含 RESULT_OKIntent 例項,且布林值額外項目 android.telephony.euicc.extra.USE_QR_SCANNER 設為 true。LPA 接著會檢查額外項目,並啟動 QR 掃描器,而不是繫結電信業者應用程式的 ICarrierEuiccProvisioningService 實作項目。
    2. 如果電信業者應用程式當機或傳回 RESULT_CANCELED (這是預設的回應代碼),LPA 會取消 eSIM 卡啟用流程。
    3. 如果電信業者應用程式傳回的內容不是 RESULT_OKRESULT_CANCELED,LPA 會將其視為錯誤。

    基於安全考量,LPA 不應直接接受結果 Intent 中提供的啟用代碼,確保非 LPA 呼叫端無法從電信業者應用程式取得啟用代碼。

在電信業者應用程式中啟動 LPA 啟用流程

自 Android 11 起,電信業者應用程式可以使用 eUICC API,啟動 eSIM 卡啟用程序的 LUI。這個方法會顯示 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 實作提供啟用代碼。電信業者應用程式會呼叫 startActivityForResult(Intent, int) 並使用 android.telephony.euicc.action.START_EUICC_ACTIVATION 動作,啟動 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 卡設定檔,LPA 會以 RESULT_CANCELED 回應。
    3. 如果 LPA 回應的內容不是 RESULT_OKRESULT_CANCELED,電信業者應用程式會將此視為錯誤。

    基於安全考量,LPA 不會直接在提供的意圖中接受啟用代碼,確保非 LPA 呼叫端無法從電信業者應用程式取得啟用代碼。

支援多張 eSIM 卡

搭載 Android 10 以上版本的裝置,EuiccManager 類別支援多個 eSIM 卡的裝置。如果裝置只有單一 eSIM 卡,升級至 Android 10 時,平台會自動將 EuiccManager 執行個體與預設 eUICC 建立關聯,因此 LPA 實作項目不需要進行任何修改。對於無線電 HAL 版本為 1.2 以上的裝置,預設 eUICC 由平台決定;對於無線電 HAL 版本低於 1.2 的裝置,預設 eUICC 由 LPA 決定。

需求條件

如要支援多張 eSIM 卡,裝置必須有多個 eUICC,可以是內建 eUICC,也可以是可插入可移除式 eUICC 的實體 SIM 卡插槽。

如要支援多張 eSIM 卡,必須使用 Radio HAL 1.2 以上版本。建議使用 Radio HAL 1.4 版和 RadioConfig HAL 1.2 版。

實作

如要支援多個 eSIM 卡 (包括可移除的 eUICC 或可程式化 SIM 卡),LPA 必須實作 EuiccService,該函式會接收與呼叫端提供的卡片 ID 相對應的插槽 ID。

non_removable_euicc_slots arrays.xml 中指定的資源是整數陣列,代表裝置內建 eUICC 的插槽 ID。您必須指定這項資源,平台才能判斷插入的 eUICC 是否可移除。

適用於有多張 eSIM 卡的裝置的電信業者應用程式

為有多張 eSIM 卡的裝置製作電信業者應用程式時,請使用 EuiccManager 中的 createForCardId 方法,建立固定至特定卡片 ID 的 EuiccManager 物件。卡片 ID 是指裝置上 UICC 或 eUICC 的專屬整數 ID。

如要取得裝置預設 eUICC 的卡片 ID,請使用 TelephonyManager 中的 getCardIdForDefaultEuicc 方法。如果無線電 HAL 版本低於 1.2,這個方法會傳回 UNSUPPORTED_CARD_ID;如果裝置尚未讀取 eUICC,則會傳回 UNINITIALIZED_CARD_ID

您也可以從 TelephonyManager 中的 getUiccCardsInfogetUiccSlotsInfo (系統 API) 取得卡片 ID,以及從 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 測試計畫