Implementing eSIM

Embedded SIM (eSIM, or eUICC) technology allows mobile users to download a carrier profile and activate a carrier's service without having a physical SIM card. It's a global specification driven by the GSMA that enables remote SIM provisioning (RSP) of any mobile device. Starting with Android 9, the Android framework provides standard APIs for accessing eSIM and managing subscription profiles on the eSIM. These eUICC APIs enable third parties to develop their own carrier apps and local profile assistants (LPAs) on eSIM-enabled Android devices.

The LPA is a standalone, system app that should be included in the Android build image. Management of the profiles on the eSIM is generally done by the LPA, as it serves as a bridge between the SM-DP+ (remote service that prepares, stores, and delivers profile packages to devices) and the eUICC chip. The LPA APK can optionally include a UI component, called the LPA UI or LUI, to provide a central place for the end user to manage all embedded subscription profiles. The Android framework automatically discovers and connects to the best available LPA, and routes all the eUICC operations through an LPA instance.

Simplified Remote SIM Provisioning (RSP) architecture

Figure 1. Simplified RSP architecture

Mobile network operators interested in creating a carrier app should look at the APIs in EuiccManager, which provides high-level profile management operations such as downloadSubscription(), switchToSubscription(), and deleteSubscription().

If you're a device OEM interested in creating your own LPA system app, you must extend EuiccService for the Android framework to connect to your LPA services. In addition, you should use the APIs in EuiccCardManager, which provide ES10x functions based on GSMA RSP v2.0. These functions are used to issue commands to the eUICC chip, such as prepareDownload(), loadBoundProfilePackage(), retrieveNotificationList(), and resetMemory().

The APIs in EuiccManager require a properly implemented LPA app to function and the caller of EuiccCardManager APIs must be an LPA. This is enforced by the Android framework.

Devices running Android 10 or higher can support devices with multiple eSIMs. For more information, see Supporting multiple eSIMs.

Making a carrier app

The eUICC APIs in Android 9 make it possible for mobile network operators to create carrier-branded apps to manage their profiles directly. This includes downloading and deleting subscription profiles owned by the carrier, as well as switching to a profile owned by a carrier.

EuiccManager

EuiccManager is the main entry point for apps to interact with the LPA. This includes carrier apps that download, delete, and switch to subscriptions owned by the carrier. This also includes the LUI system app, which provides a central location/UI for managing all embedded subscriptions, and can be a separate app from the one that provides the EuiccService.

To use the public APIs, a carrier app must first obtain the instance of EuiccManager through Context#getSystemService:

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

You should check whether eSIM is supported on the device before performing any eSIM operations. EuiccManager#isEnabled() generally returns true if the android.hardware.telephony.euicc feature is defined and an LPA package is present.

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

To get information about the eUICC hardware and the eSIM OS version:

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

Many APIs, such as downloadSubscription() and switchToSubscription(), use PendingIntent callbacks as they may take seconds or even minutes to complete. PendingIntent is sent with a result code in the EuiccManager#EMBEDDED_SUBSCRIPTION_RESULT_ space, which provides framework-defined error codes, as well as an arbitrary detailed result code propagated from the LPA as EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE, allowing the carrier app to track for logging/debugging purposes. The PendingIntent callback must be BroadcastReceiver.

To download a given downloadable subscription (created from an activation code or a 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);

Define and use the permission in AndroidManifest.xml:

    <permission android:protectionLevel="signature" android:name="com.your.company.lpa.permission.BROADCAST" />
    <uses-permission android:name="com.your.company.lpa.permission.BROADCAST"/>

To switch to a subscription given the subscription 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);

For a complete list of EuiccManager APIs and code examples, see eUICC APIs.

Resolvable errors

There are some cases where the system is unable to complete the eSIM operation but the error can be resolved by the user. For example, downloadSubscription may fail if the profile metadata indicates that a carrier confirmation code is required. Or switchToSubscription may fail if the carrier app has carrier privileges over the destination profile (that is, carrier owns the profile) but doesn't have carrier privileges over the currently enabled profile, and hence user consent is required.

For these cases, the caller's callback is called with EuiccManager#EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR. The callback Intent contains internal extras such that when the caller passes it to EuiccManager#startResolutionActivity, resolution can be requested through the LUI. Using the confirmation code for example again, EuiccManager#startResolutionActivity triggers an LUI screen that allows the user to enter a confirmation code; after the code is entered, the download operation is resumed. This approach provides the carrier app with full control over when the UI is shown, but gives the LPA/LUI an extensible method for adding new handling of user-recoverable issues in the future without needing client apps to change.

Android 9 defines these resolvable errors in EuiccService, which the LUI should handle:

/**
 * 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";

Carrier privileges

If you're a carrier developing your own carrier app that calls EuiccManager to download profiles onto a device, your profile should include carrier privilege rules corresponding to your carrier app in the metadata. This is because subscription profiles belonging to different carriers can co-exist in the eUICC of a device, and each carrier app should only be allowed to access the profiles owned by that carrier. For example, carrier A should not be able to download, enable, or disable a profile owned by carrier B.

To ensure a profile is only accessible to its owner, Android uses a mechanism to grant special privileges to the profile owner's app (that is, carrier app). The Android platform loads certificates stored in the profile's access rule file (ARF) and grants permission to apps signed by these certificates to make calls to EuiccManager APIs. The high-level process is described below:

  1. Operator signs the carrier app APK; the apksigner tool attaches the public-key certificate to the APK.
  2. Operator/SM-DP+ prepares a profile and its metadata, which include an ARF that contains:

    1. Signature (SHA-1 or SHA-256) of the carrier app's public-key certificate (required)
    2. Package name of the carrier app (strongly recommended)
  3. Carrier app tries to perform an eUICC operation via EuiccManager API.

  4. Android platform verifies SHA-1 or SHA-256 hash of the caller app's certificate matches the signature of the certificate obtained from the target profile's ARF. If the package name of the carrier app is included in the ARF, it must also match the package name of the caller app.

  5. After the signature and the package name (if included) are verified, the carrier privilege is granted to the caller app over the target profile.

Because profile metadata can be available outside of the profile itself (so that LPA can retrieve the profile metadata from SM-DP+ before the profile is downloaded, or from ISD-R when the profile is disabled), it should contain the same carrier privilege rules as in the profile.

The eUICC OS and SM-DP+ must support a proprietary tag BF76 in the profile metadata. The tag content should be the same carrier privilege rules as returned by the access rule applet (ARA) defined in UICC Carrier Privileges:

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
    }
}

For more details on app signing, see Sign your app. For details on carrier privileges, see UICC Carrier Privileges.

Making a local profile assistant app

Device manufacturers can implement their own local profile assistant (LPA), which must be hooked up with Android Euicc APIs. The following sections give a brief overview of making an LPA app and integrating it with the Android system.

Hardware/modem requirements

The LPA and the eSIM OS on the eUICC chip must support at least GSMA RSP (Remote SIM Provisioning) v2.0 or v2.2. You should also plan to use SM-DP+ and SM-DS servers that have a matching RSP version. For detailed RSP architecture, see GSMA SGP.21 RSP Architecture Specification.

In addition, to integrate with the eUICC APIs in Android 9, the device modem should send terminal capabilities with the support for eUICC capabilities encoded (local profile management and profile download). It also needs to implement the following methods:

  • IRadio HAL v1.1: setSimPower
  • IRadio HAL v1.2: getIccCardStatus

  • IRadioConfig HAL v1.0: getSimSlotsStatus

  • IRadioConfig AIDL v1.0: getAllowedCarriers

    The Google LPA needs to know the carrier lock status so that it can allow eSIM download or transfer only for the allowed carrier. Otherwise users may end up downloading and transferring a SIM and later realize the device is carrier locked to a different carrier.

    • Vendors or OEMs must implement the IRadioSim.getAllowedCarriers()HAL API.

    • Vendor RIL / Modem shall populate the lock status and carrierId of the carrier where the device is locked to as part of the IRadioSimResponse.getAllowedCarriersResponse()HAL API.

The modem should recognize the eSIM with the default boot profile enabled as a valid SIM and keep the SIM power on.

For devices running Android 10, a nonremovable eUICC slot ID array must be defined. For example, see 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>

For a complete list of modem requirements, see Modem Requirements for eSIM Support.

EuiccService

An LPA consists of two separate components (may both be implemented in the same APK): the LPA backend, and the LPA UI or LUI.

To implement the LPA backend, you must extend EuiccService and declare this service in your manifest file. The service must require the android.permission.BIND_EUICC_SERVICE system permission to ensure that only the system can bind to it. The service must also include an intent filter with the android.service.euicc.EuiccService action. The priority of the intent filter should be set to a non-zero value in case multiple implementations are present on the device. For example:

<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>

Internally, the Android framework determines the active LPA and interacts with it as needed to support the Android eUICC APIs. PackageManager is queried for all apps with the android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS permission, which specifies a service for the android.service.euicc.EuiccService action. The service with the highest priority is selected. If no service is found, LPA support is disabled.

To implement the LUI, you must provide an activity for the following actions:

  • android.service.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS
  • android.service.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION

As with the service, each activity must require the android.permission.BIND_EUICC_SERVICE system permission. Each should have an intent filter with the appropriate action, the android.service.euicc.category.EUICC_UI category, and a non-zero priority. Similar logic is used to pick the implementations for these activities as with picking the implementation of EuiccService. For example:

<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>

This implies that the UI implementing these screens can come from a different APK from the one that implements EuiccService. Whether to have a single APK or multiple APKs (for example, one that implements EuiccService and one that provides LUI activities) is a design choice.

EuiccCardManager

EuiccCardManager is the interface for communicating with the eSIM chip. It provides ES10 functions (as described in the GSMA RSP spec) and handles the low-level APDU request/response commands as well as ASN.1 parsing. EuiccCardManager is a system API and can be called only by system-privileged apps.

Carrier apps, LPA, and Euicc APIs

Figure 2. Both carrier app and LPA use Euicc APIs

The profile operation APIs through EuiccCardManager require the caller to be an LPA. This is enforced by the Android framework. This means the caller must extend EuiccService and be declared in your manifest file, as described in the previous sections.

Similar to EuiccManager, to use the EuiccCardManager APIs, your LPA must first obtain the instance of EuiccCardManager through Context#getSystemService:

EuiccCardManager cardMgr = (EuiccCardManager) context.getSystemService(Context.EUICC_CARD_SERVICE);

Then, to get all the profiles on the 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);

Internally, EuiccCardManager binds to EuiccCardController (which runs in the phone process) through an AIDL interface, and each EuiccCardManager method receives its callback from the phone process through a different, dedicated AIDL interface. When using EuiccCardManager APIs, the caller (LPA) must provide an Executor object through which the callback is invoked. This Executor object may run on a single thread or on a thread pool of your choice.

Most EuiccCardManager APIs have the same usage pattern. For example, to load a bound profile package onto the eUICC:

...
cardMgr.loadBoundProfilePackage(eid, boundProfilePackage,
        AsyncTask.THREAD_POOL_EXECUTOR, callback);

To switch to a different profile with a given ICCID:

...
cardMgr.switchToProfile(eid, iccid, true /* refresh */,
        AsyncTask.THREAD_POOL_EXECUTOR, callback);

To get the default SM-DP+ address from the eUICC chip:

...
cardMgr.requestDefaultSmdpAddress(eid, AsyncTask.THREAD_POOL_EXECUTOR,
        callback);

To retrieve a list of notifications of the given notification events:

...
cardMgr.listNotifications(eid,
        EuiccNotification.Event.INSTALL
              | EuiccNotification.Event.DELETE /* events */,
        AsyncTask.THREAD_POOL_EXECUTOR, callback);

Activating an eSIM profile through a carrier app

On devices running Android 9 or higher, you can use a carrier app to activate the eSIM and download profiles. The carrier app can download profiles by calling downloadSubscription directly or by providing an activation code to the LPA.

When a carrier app downloads a profile by calling downloadSubscription, the call enforces that the app can manage the profile through a BF76 metadata tag that encodes carrier privilege rules for the profile. If a profile doesn't have a BF76 tag or if its BF76 tag doesn't match the calling carrier app's signature, the download is rejected.

The section below describes activating an eSIM through a carrier app using an activation code.

Activating eSIM using an activation code

When using an activation code to activate an eSIM profile, the LPA fetches an activation code from the carrier app and downloads the profile. This flow can be initiated by the LPA and the LPA can control the entire UI flow, meaning that no carrier app UI is shown. This approach bypasses the BF76 tag check, and network operators don't need to implement the entire eSIM activation UI flow including downloading an eSIM profile and error handling.

Defining the carrier eUICC provisioning service

The LPA and carrier app communicate through two AIDL interfaces: ICarrierEuiccProvisioningService and IGetActivationCodeCallback. The carrier app must implement an ICarrierEuiccProvisioningService interface and expose it in its manifest declaration. The LPA must bind to ICarrierEuiccProvisioningService and implement IGetActivationCodeCallback. For more information on how to implement and expose an AIDL interface, see Defining and AIDL interface.

To define the AIDL interfaces, create the following AIDL files for both the LPA and carrier apps.

  • 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();
    }
    

Example LPA implementation

To bind to the carrier app's ICarrierEuiccProvisioningService implementation, the LPA must copy both ICarrierEuiccProvisioningService.aidl and IGetActivationCodeCallback.aidl to your project and implement ServiceConnection.

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

After binding to the carrier app's ICarrierEuiccProvisioningService implementation, the LPA calls either getActivationCode or getActivationCodeForEid to get the activation code from the carrier app by passing the implementation of the IGetActivationCodeCallback stub class.

The difference between getActivationCode and getActivationCodeForEid is that getActivationCodeForEid allows a carrier to pre-bind a profile to the device's EID before the download process begins.

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
    }
}

Example implementation for carrier app

For the LPA to bind to the carrier app, the carrier app must copy both ICarrierEuiccProvisioningService.aidl and IGetActivationCodeCallback.aidl to your project and declare the ICarrierEuiccProvisioningService service in the AndroidManifest.xml file. The service must require the android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS system permission to ensure that only the LPA, a system-privileged app, can bind to it. The service must also include an intent filter with the android.service.euicc.action.BIND_CARRIER_PROVISIONING_SERVICE action.

  • 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>
    

To implement the AIDL carrier app service, create a service, extend the Stub class and implement the getActivationCode and getActivationCodeForEid methods. The LPA can then call either method to fetch the profile activation code. The carrier app should respond by calling IGetActivationCodeCallback#onSuccess with the activation code if the code was fetched from the carrier's server successfully. If unsuccessful, the carrier app should respond with 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);
              }
          }
    }
    

Starting the carrier app UI in the LPA activation flow

On devices running Android 11 and higher, the LPA can start a carrier app's UI. This is useful as a carrier app might require additional information from the user before supplying an activation code to the LPA. For example, carriers might require users to log in to activate their phone numbers or perform other porting services.

This is the process for starting a carrier app's UI in the LPA:

  1. The LPA launches the carrier app's activation flow by sending the android.service.euicc.action.START_CARRIER_ACTIVATION intent to the carrier app package containing the action. (The carrier app receiver must be protected in the manifest declaration with android:permission="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" to avoid receiving intents from non-LPA apps.)

    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. The carrier app does its work using its own UI. For example, logging in the user or sending HTTP requests to the carrier's backend.

  3. The carrier app responds to the LPA by calling setResult(int, Intent) and finish().

    1. If the carrier app responds with RESULT_OK, the LPA continues the activation flow. If the carrier app determines that the user should scan a QR code instead of letting the LPA bind the carrier app's service, the carrier app responds to the LPA using setResult(int, Intent) with RESULT_OK and an Intent instance containing the boolean extra android.telephony.euicc.extra.USE_QR_SCANNER set to true. The LPA then checks the extra and launches the QR scanner instead of binding the carrier app's ICarrierEuiccProvisioningService implementation.
    2. If the carrier app crashes or responds with RESULT_CANCELED (this is the default response code), the LPA cancels the eSIM activation flow.
    3. If the carrier app responds with something other than RESULT_OK or RESULT_CANCELED, the LPA treats it as an error.

    For security reasons, the LPA should not directly accept an activation code supplied in the result intent to ensure that non-LPA callers can't get an activation code from the carrier app.

Launching the LPA activation flow in a carrier app

Starting in Android 11, carrier apps can use eUICC APIs to start an LUI for eSIM activation. This method surfaces the LPA's eSIM activation flow UI to activate the eSIM profile. The LPA then sends a broadcast when the eSIM profile activation finishes.

  1. The LPA must declare an activity including an intent filter with the android.service.euicc.action.START_EUICC_ACTIVATION action. The priority of the intent filter should be set to a non-zero value in case multiple implementations are present on the device. For example:

    <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. The carrier app does its work using its own UI. For example, logging in the user or sending HTTP requests to the carrier's backend.

  3. At this point, the carrier app must be ready to supply an activation code through its ICarrierEuiccProvisioningService implementation. The carrier app launches the LPA by calling startActivityForResult(Intent, int) with the android.telephony.euicc.action.START_EUICC_ACTIVATION action. The LPA also checks the boolean extra android.telephony.euicc.extra.USE_QR_SCANNER. If the value is true, the LPA launches the QR scanner to let the user scan the profile QR code.

  4. On the LPA side, the LPA binds to the carrier app's ICarrierEuiccProvisioningService implementation to fetch the activation code and download the corresponding profile. The LPA displays all necessary UI elements during the download, such as a loading screen.

  5. When the LPA activation flow is complete, the LPA responds to the carrier app with a result code, which the carrier app handles in onActivityResult(int, int, Intent).

    1. If the LPA succeeds in downloading the new eSIM profile, it responds with RESULT_OK.
    2. If the user cancels the eSIM profile activation in the LPA, it responds with RESULT_CANCELED.
    3. If the LPA responds with something other than RESULT_OK or RESULT_CANCELED, the carrier app treats this as an error.

    For security reasons, the LPA does not accept an activation code directly in the supplied intent to ensure that non-LPA callers can't get the activation code from the carrier app.

Supporting multiple eSIMs

For devices running Android 10 or higher, the EuiccManager class supports devices with multiple eSIMs. Devices with a single eSIM that are upgrading to Android 10 don't require any modification to the LPA implementation as the platform automatically associates the EuiccManager instance with the default eUICC. The default eUICC is determined by the platform for devices with radio HAL version 1.2 or higher and by the LPA for devices with radio HAL versions lower than 1.2.

Requirements

To support multiple eSIMs, the device must have more than one eUICC, which can be either a built-in eUICC or a physical SIM slot where removable eUICCs can be inserted.

Radio HAL version 1.2 or higher is required to support multiple eSIMs. Radio HAL version 1.4 and RadioConfig HAL version 1.2 are recommended.

Implementation

To support multiple eSIMs (including removable eUICCs or programmable SIMs), the LPA must implement EuiccService, which receives the slot ID corresponding to the caller-provided card ID.

The non_removable_euicc_slots resource specified in arrays.xml is an array of integers that represent the slot IDs of a device's built-in eUICCs. You must specify this resource to allow the platform to determine whether an inserted eUICC is removable or not.

Carrier app for device with multiple eSIMs

When making a carrier app for a device with multiple eSIMs, use the createForCardId method in EuiccManager to create an EuiccManager object that is pinned to a given card ID. The card ID is an integer value that uniquely identifies a UICC or an eUICC on the device.

To get the card ID for the device's default eUICC, use the getCardIdForDefaultEuicc method in TelephonyManager. This method returns UNSUPPORTED_CARD_ID if the radio HAL version is lower than 1.2 and returns UNINITIALIZED_CARD_ID if the device hasn't read the eUICC.

You can also get card IDs from getUiccCardsInfo and getUiccSlotsInfo (system API) in TelephonyManager, and getCardId in SubscriptionInfo.

When an EuiccManager object has been instantiated with a specific card ID, all operations are directed to the eUICC with that card ID. If the eUICC becomes unreachable (for example, when it is turned off or removed) EuiccManager no longer works.

You can use the following code samples to create a carrier app.

Example 1: Get active subscription and instantiate 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);

Example 2: Iterate through UICCs and instantiate EuiccManager for a removable eUICC

// On a device with a built-in eUICC and a removable eUICC, iterate through the UICC cards
// to instantiate an EuiccManager associated with a removable eUICC
TelephonyManager telMan = (TelephonyManager)
        mContext.getSystemService(Context.TELEPHONY_SERVICE);
List<UiccCardInfo> infos = telMan.getUiccCardsInfo();
int removableCardId = -1; // valid cardIds are 0 or greater
for (UiccCardInfo info : infos) {
    if (info.isRemovable()) {
        removableCardId = info.getCardId();
        break;
    }
}
if (removableCardId != -1) {
    EuiccManager euiccMan = (EuiccManager) mContext.getSystemService(Context.EUICC_SERVICE)
            .createForCardId(removableCardId);
}

Validation

AOSP doesn't come with an LPA implementation and you aren't expected to have an LPA available on all Android builds (not every phone supports eSIM). For this reason, there are no end-to-end CTS test cases. However, basic test cases are available in AOSP to ensure the exposed eUICC APIs are valid in Android builds.

You should make sure the builds pass the following CTS test cases (for public APIs): /platform/cts/tests/tests/telephony/current/src/android/telephony/euicc/cts.

Carriers implementing a carrier app should go through their normal in-house quality assurance cycles to ensure all implemented features are working as expected. At the minimum, the carrier app should be able to list all the subscription profiles owned by the same operator, download and install a profile, activate a service on the profile, switch between profiles, and delete profiles.

If you're making your own LPA, you should go through much more rigorous testing. You should work with your modem vendor, eUICC chip or eSIM OS vendor, SM-DP+ vendors, and carriers to resolve issues and ensure interoperability of your LPA within the RSP architecture. A good amount of manual testing is inevitable. For best test coverage, you should follow the GSMA SGP.23 RSP Test Plan.