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.
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.
Make 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:
- Operator signs the carrier app APK; the apksigner tool attaches the public-key certificate to the APK.
Operator/SM-DP+ prepares a profile and its metadata, which include an ARF that contains:
- Signature (SHA-1 or SHA-256) of the carrier app's public-key certificate (required)
- Package name of the carrier app (strongly recommended)
Carrier app tries to perform an eUICC operation with the
EuiccManager
API.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.
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.
Make 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.
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);
Activate 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.
Activate 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.
Define 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); } } }
Start 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:
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 withandroid: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);
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.
The carrier app responds to the LPA by calling
setResult(int, Intent)
andfinish()
.- 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 usingsetResult(int, Intent)
withRESULT_OK
and anIntent
instance containing the boolean extraandroid.telephony.euicc.extra.USE_QR_SCANNER
set totrue
. The LPA then checks the extra and launches the QR scanner instead of binding the carrier app'sICarrierEuiccProvisioningService
implementation. - If the carrier app crashes or responds with
RESULT_CANCELED
(this is the default response code), the LPA cancels the eSIM activation flow. - If the carrier app responds with something other than
RESULT_OK
orRESULT_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.
- If the carrier app responds with
Launch 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.
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>
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.
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 callingstartActivityForResult(Intent, int)
with theandroid.telephony.euicc.action.START_EUICC_ACTIVATION
action. The LPA also checks the boolean extraandroid.telephony.euicc.extra.USE_QR_SCANNER
. If the value istrue
, the LPA launches the QR scanner to let the user scan the profile QR code.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.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)
.- If the LPA succeeds in downloading the new eSIM profile, it
responds with
RESULT_OK
. - If the user cancels the eSIM profile activation in the LPA, it
responds with
RESULT_CANCELED
. - If the LPA responds with something other than
RESULT_OK
orRESULT_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.
- If the LPA succeeds in downloading the new eSIM profile, it
responds with
Support 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.