eSIM を実装する

埋め込み SIM(eSIM、eUICC)は、モバイル ユーザーが物理的な SIM カードを持っていなくても、携帯通信会社のプロファイルをダウンロードしてサービスを有効にできる技術です。GSMA が主導するグローバル仕様であり、モバイル デバイスのリモート SIM プロビジョニング(RSP)を可能にします。Android 9 以降の Android フレームワークには、eSIM にアクセスして eSIM のサブスクリプション プロファイルを管理するための標準 API が用意されています。サードパーティはこの eUICC API を使用して、eSIM 対応の Android デバイスで独自の携帯通信会社アプリとローカル プロファイル アシスタント(LPA)を開発できます。

LPA はスタンドアロンのシステムアプリであり、Android のビルドイメージに含める必要があります。eSIM 上のプロファイルの管理は通常、SM-DP+(プロファイル パッケージを準備、保管して、デバイスに配信するリモート サービス)と eUICC チップの間のブリッジとして機能する LPA によって行われます。LPA APK には、必要に応じて LPA UI または LUI と呼ばれる UI コンポーネントを含めることができ、エンドユーザーはこれを使用してすべての埋め込みサブスクリプション プロファイルを一元管理できます。Android フレームワークは利用可能な最良の LPA を自動的に検出して接続し、すべての eUICC オペレーションを LPA インスタンス経由でルーティングします。

簡略化したリモート SIM プロビジョニング(RSP)アーキテクチャ

図 1. 簡略化した RSP アーキテクチャ

携帯通信会社アプリの作成を検討中のモバイル ネットワーク事業者の方は、downloadSubscription()switchToSubscription()deleteSubscription() などのハイレベルなプロファイル管理操作を提供する EuiccManager の API をご覧ください。

独自の LPA システムアプリの作成を検討中のデバイス OEM の方は、Android フレームワークが LPA サービスに接続できるように EuiccService を拡張する必要があります。さらに、GSMA RSP v2.0 に基づく ES10x 関数を提供する、EuiccCardManager の API を使用してください。これらの関数は、prepareDownload()loadBoundProfilePackage()retrieveNotificationList()resetMemory() のように、eUICC チップに対するコマンドの発行に使用されます。

EuiccManager の API を使用するには、適切に実装された LPA アプリが機能している必要があり、EuiccCardManager API は LPA によって呼び出される必要があります。これは、Android フレームワークによって強制されます。

Android 10 以降を搭載したデバイスは、複数の eSIM を備えたデバイスに対応しています。詳しくは、複数の eSIM のサポートをご覧ください。

携帯通信会社アプリの作成

Android 9 の eUICC API を使用すると、モバイル ネットワーク事業者が携帯通信会社ブランドのアプリを作成してプロファイルを直接管理できます。携帯通信会社が所有するサブスクリプション プロファイルのダウンロードと削除のほか、携帯通信会社が所有するプロファイルへの切り替えも可能です。

EuiccManager

EuiccManager は、アプリが LPA とやり取りするためのメインのエントリ ポイントです。これには、携帯通信会社が所有するサブスクリプションのダウンロード、削除、切り替えを行う携帯通信会社アプリが含まれます。さらに、すべての埋め込みサブスクリプションを一元管理できる場所または UI を提供する LUI システムアプリ(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();

情報の取得には数秒から数分を必要とするため、downloadSubscription()switchToSubscription() などの多くの API では、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 を通じて解決策をリクエストできます。ここでも確認コードを例にすると、ユーザーが確認コードを入力できる LUI 画面が EuiccManager#startResolutionActivity によってトリガーされ、コードの入力後はダウンロードが再開されます。この方法では、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. 署名とパッケージ名(含まれている場合)の確認後、ターゲット プロファイルを介して携帯通信会社の権限が呼び出し元アプリに付与されます。

プロファイル メタデータはプロファイル外で使用できる(つまり、プロファイルをダウンロードする前に 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 システムとの統合について簡単に説明します。

ハードウェアとモデムの要件

LPA と eUICC チップ上の eSIM OS は、少なくとも GSMA RSP(リモート SIM プロビジョニング)v2.0 または v2.2 をサポートしている必要があります。また、RSP バージョンが一致する SM-DP+ サーバーと SM-DS サーバーも使用する必要があります。RSP アーキテクチャについて詳しくは、GSMA SGP.21 RSP Architecture Specification をご覧ください。

また、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

    許可されている携帯通信会社だけが eSIM のダウンロードや移行を行えるように、携帯通信会社のロックのステータスを Google の LPA が把握している必要があります。把握していない場合、ユーザーが eSIM のダウンロードや移行を行った後に、デバイスがキャリアロックされていて、別の携帯通信会社を利用できないことが判明するという事態になりかねません。

    • ベンダーや OEM は 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 に実装できる 2 つの個別のコンポーネント(LPA バックエンドと LPA UI または LUI)で構成されます。

LPA バックエンドを実装するには、EuiccService を拡張して、マニフェスト ファイルでこのサービスを宣言してください。システムのみがバインドできるように、サービスは android.permission.BIND_EUICC_SERVICE システム権限を要求する必要があります。サービスには、android.service.euicc.EuiccService アクションを指定したインテント フィルタも含める必要があります。複数の実装がデバイスに混在する場合は、インテント フィルタの優先度を 0 以外の値に設定してください。次に例を示します。

<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 をサポートします。PackageManagerandroid.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 カテゴリ、0 以外の優先度がそれぞれに必要です。これらのアクティビティの実装は、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 チップと通信するためのインターフェースです。GSMA RSP 仕様に記述されている ES10 関数を提供し、低レベルの 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 オブジェクトは、1 つのスレッドまたは任意のスレッドプールで実行できます。

ほとんどの 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 プロファイルのダウンロードやエラー処理などの eSIM 有効化の UI フロー全体を実装する必要はありません。

携帯通信会社の eUICC プロビジョニング サービスの定義

LPA と携帯通信会社アプリは、2 つの 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 の実装例

LPA が携帯通信会社アプリの ICarrierEuiccProvisioningService の実装にバインドするには、ICarrierEuiccProvisioningService.aidlIGetActivationCodeCallback.aidl の両方をプロジェクトにコピーして、ServiceConnection を実装する必要があります。

@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
    mCarrierProvisioningService = ICarrierEuiccProvisioningService.Stub.asInterface(iBinder);
}

携帯通信会社アプリの ICarrierEuiccProvisioningService の実装にバインドした後、LPA は getActivationCode または getActivationCodeForEid を呼び出して、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 の両方をプロジェクトにコピーし、ICarrierEuiccProvisioningService サービスを AndroidManifest.xml ファイル内で宣言する必要があります。システム権限アプリである LPA だけがこのサービスにバインドできるように、このサービスは android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS システム権限を要求する必要があります。サービスには、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 クラスを拡張して、getActivationCode メソッドと getActivationCodeForEid メソッドを実装します。その後、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 に携帯通信会社アプリのサービスにバインドさせる代わりに、ユーザーに QR コードをスキャンさせる必要があると携帯通信会社アプリが判断した場合、携帯通信会社アプリは RESULT_OK を指定した setResult(int, Intent) と、true に設定されたブール型のエクストラ android.telephony.euicc.extra.USE_QR_SCANNER を含む Intent インスタンスを使って LPA に応答します。LPA はそのエクストラをチェックし、携帯通信会社アプリの ICarrierEuiccProvisioningService の実装をバインドする代わりに、QR スキャナを起動します。
    2. 携帯通信会社アプリがクラッシュした場合、または RESULT_CANCELED(これはデフォルトのレスポンス コードです)で応答した場合、LPA は eSIM の有効化フローをキャンセルします。
    3. 携帯通信会社アプリが RESULT_OKRESULT_CANCELED 以外で応答した場合、LPA はエラーとして処理します。

    セキュリティ上の理由から、LPA は結果のインテントに供給されるアクティベーション コードを直接受け取らないようにして、LPA 以外の呼び出し元が携帯通信会社アプリからアクティベーション コードを取得できないようにします。

携帯通信会社アプリでの LPA 有効化フローの開始

Android 11 以降では、携帯通信会社アプリは eUICC API を使用して eSIM 有効化の LUI を起動できます。この方法では、LPA の eSIM 有効化フローの UI を表示して、eSIM プロファイルを有効にします。LPA は、eSIM プロファイルの有効化が終了したときにブロードキャストを送信します。

  1. LPA は android.service.euicc.action.START_EUICC_ACTIVATION アクションが指定されたインテント フィルタを含むアクティビティを宣言する必要があります。複数の実装がデバイスに混在する場合は、インテント フィルタの優先度を 0 以外の値に設定してください。次に例を示します。

    <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 側では、携帯通信会社アプリの ICarrierEuiccProvisioningService の実装にバインドして、アクティベーション コードを取得し、対応するプロファイルをダウンロードします。ダウンロード中に必要になる UI 要素(ダウンロード中の画面など)はすべて LPA が表示します。

  5. 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 の実装を変更する必要はありません。デフォルトの eUICC は、Radio HAL バージョン 1.2 以降を備えたデバイスの場合はプラットフォームによって、バージョン 1.2 より前の Radio HAL を備えたデバイスの場合は 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 を受け取る必要があります。

arrays.xml で指定されている non_removable_euicc_slots リソースは、デバイスの組み込み eUICC のスロット ID を表す整数の配列です。挿入されている eUICC がリムーバブルかどうかをプラットフォームが判別できるように、このリソースを指定する必要があります。

複数の eSIM を内蔵したデバイス向けの携帯通信会社アプリ

複数の eSIM を内蔵したデバイス向けの携帯通信会社アプリを作成する場合は、EuiccManagercreateForCardId メソッドを使用して、特定のカード ID に固定される EuiccManager オブジェクトを作成します。カード ID は、デバイス上の UICC または eUICC を一意に識別する整数値です。

デバイスのデフォルト eUICC のカード ID を取得するには、TelephonyManagergetCardIdForDefaultEuicc メソッドを使用します。このメソッドは、Radio HAL バージョンが 1.2 より前である場合は UNSUPPORTED_CARD_ID を返し、デバイスが eUICC を読み取っていない場合は UNINITIALIZED_CARD_ID を返します。

カード ID は、TelephonyManagergetUiccCardsInfogetUiccSlotsInfo(システム API)、または SubscriptionInfogetCardId から取得することもできます。

EuiccManager オブジェクトが特定のカード ID でインスタンス化されている場合、すべての操作はそのカード 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+ ベンダー、携帯通信会社と協力して問題を解決し、RSP アーキテクチャ内での LPA の相互運用性を確保してください。大量の手動テストが必要となります。最適なテスト カバレッジについては、GSMA SGP.23 RSP Test Plan をご覧ください。