# Android eSIM Management: EuiccManager vs EuiccCardManager

## Why Android's eSIM API Architecture Matters

If you are building an eSIM feature into an Android app, the first thing you will discover is that the platform gives you two very different APIs to work with. They look similar on the surface — both are system services, both deal with eSIM profiles — but they operate at completely different levels of abstraction and serve completely different callers.、

![](https://cdn.hashnode.com/uploads/covers/6aaa115a64bb89e96cb64053/19147d31-6946-4de9-bc5a-fde0dd0a3a94.webp align="center")

This distinction matters more than most developers realize. Use the wrong API in the wrong context, and your calls will fail silently or throw security exceptions before you even reach the eUICC chip. Understanding when to use `EuiccManager` and when to reach for `EuiccCardManager` is the difference between a working integration and a week of debugging.

[Roami](https://www.roamiapp.com/)'s eSIM platform abstracts much of this complexity away for developers, providing a REST API that handles the entire profile provisioning workflow. But if you are building a carrier app, an LPA, or a travel app with deep eSIM integration, understanding Android's native eUICC APIs is essential.

* * *

## The Two-Layer Architecture

Android's eUICC support is built on a clear separation of concerns.

**EuiccManager** is the public-facing API, available in the `android.telephony.euicc` package. It is designed for **carrier apps** — applications that need to download, switch, or delete eSIM profiles on behalf of a mobile carrier. Carrier apps do not need to be system apps, but they must have **carrier privileges** granted by the eUICC profile itself. These privileges are embedded in the profile metadata and enforced by the platform.

**EuiccCardManager** is a system API, accessible only to applications with system privileges. It provides **ES10x functions** based on GSMA RSP v2.0 — the low-level commands that actually talk to the eUICC chip. The caller of `EuiccCardManager` must be an LPA (Local Profile Assistant), and this is enforced by the Android framework. An LPA is the software component that handles the actual communication with the eUICC, including profile download, installation, and notification handling.

The relationship between the two is straightforward: **EuiccManager calls go through the LPA**, which in turn uses **EuiccCardManager** to issue commands to the eUICC chip. A carrier app using `EuiccManager.downloadSubscription()` is essentially delegating the profile download to the active LPA, which handles the SM-DP+ interaction and eUICC communication on the app's behalf.

* * *

## Working with EuiccManager

### Getting Started

`EuiccManager` is obtained through the standard Android system service mechanism:

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

Before calling any eUICC APIs, you should check whether embedded subscriptions are enabled:

```java
if (!mgr.isEnabled()) {
    // eUICC is not available or has been disabled
    // (e.g., due to carrier restrictions from a physical SIM)
    return;
}
```

This check is important because even devices with the `FEATURE_TELEPHONY_EUICC` feature may have embedded subscriptions turned off at runtime.

### Checking Carrier Privileges

Carrier privileges are the gatekeeper for `EuiccManager` operations. They are granted by the mobile carrier through profile metadata, and the eUICC API enforces these rules automatically.

For profile download operations, the carrier app must have privileges that match the target profile. If the profile's BF76 metadata tag is missing or does not match the calling app's signature, the download will be rejected.

### Downloading a Profile

The core operation for carrier apps is `downloadSubscription()`. This method takes a `DownloadableSubscription` object and a boolean indicating whether to switch to the profile after download:

```java
DownloadableSubscription sub = DownloadableSubscription
    .forActivationCode("LPA:1$SMDP_ADDRESS$ACTIVATION_TOKEN");

Intent intent = new Intent(action);
PendingIntent callbackIntent = PendingIntent.getBroadcast(
    getContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
mgr.downloadSubscription(sub, true /* switchAfterDownload */, callbackIntent);
```

Because this operation can take seconds or even minutes, it uses a `PendingIntent` callback rather than a synchronous return. You register a `BroadcastReceiver` to handle the result:

```java
String action = "download_subscription";
BroadcastReceiver receiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (!action.equals(intent.getAction())) return;
        int resultCode = getResultCode();
        int detailedCode = intent.getIntExtra(
            EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE, 0);
        // Handle result
    }
};
context.registerReceiver(receiver, new IntentFilter(action), 
    "example.broadcast.permission", null);
```

### Switching Between Profiles

Switching to an already-installed profile follows the same asynchronous pattern:

```java
mgr.switchToSubscription(1 /* subscriptionId */, 
    callbackIntent);
```

Starting with Android 13, you can specify a port index when switching, enabling support for **Multiple Enabled Profiles (MEP)** — the ability to have more than one eSIM profile active simultaneously:

```java
mgr.switchToSubscription(1 /* subscriptionId */, 
    0 /* portIndex */, callbackIntent);
```

This is a significant capability for dual-SIM use cases, where a user might want a travel eSIM active alongside their home carrier profile.

* * *

## Working with EuiccCardManager

### Who Can Use It

`EuiccCardManager` is strictly for **LPA applications** — system apps that implement the `EuiccService` and are declared in the device's system image. If your app is not a system app, you cannot use this API, full stop.

The LPA is responsible for far more than just calling `EuiccCardManager`. It must implement:

*   An **SM-DP+ client** to authenticate with and download profiles from the carrier's server
    
*   **Notification handling** to update the server on profile state changes
    
*   Optional **slot management** for switching between eSIM and physical SIM
    
*   **eSIM OTA** update handling
    

### The ES10x Command Set

`EuiccCardManager` exposes the GSMA RSP v2.0 ES10x functions. The key methods include:

*   `prepareDownload()` — prepares the eUICC to receive a profile download
    
*   `loadBoundProfilePackage()` — loads an encrypted profile package onto the chip
    
*   `requestAllProfiles()` — retrieves the list of all installed profiles
    
*   `switchToProfile()` — activates a specific profile by ICCID
    
*   `listNotifications()` — retrieves pending notification events
    
*   `resetMemory()` — clears the eUICC's profile memory
    

### The Usage Pattern

Most `EuiccCardManager` APIs follow the same asynchronous pattern. You provide an `Executor` for the callback thread, and a `ResultCallback` to receive the result:

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

ResultCallback<EuiccProfileInfo[]> callback = 
    new ResultCallback<EuiccProfileInfo[]>() {
        @Override
        public void onComplete(int resultCode, EuiccProfileInfo[] result) {
            if (resultCode == EuiccCardManagerReflector.RESULT_OK) {
                // Process profiles
            } else {
                // Handle error
            }
        }
    };

cardMgr.requestAllProfiles(eid, AsyncTask.THREAD_POOL_EXECUTOR, callback);
```

Internally, `EuiccCardManager` binds to the `EuiccCardController` running in the telephony process via AIDL, and each method receives callbacks through a dedicated AIDL interface.

To load a bound profile package — the encrypted, EID-bound profile that the SM-DP+ generates:

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

And to switch to a specific profile by ICCID:

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

* * *

## Handling Errors and Edge Cases

The eUICC APIs provide detailed error codes to help diagnose failures. Starting from Android 11, the result intent includes four keys for granular error handling:

*   **OPERATION\_CODE** — what operation was attempted (download, delete, switch)
    
*   **ERROR\_CODE** — why the operation failed (timeout, carrier locked, etc.)
    
*   **SMDX\_SUBJECT\_CODE** and **SMDX\_REASON\_CODE** — GSMA SGP.22 v2.2 Subject and Reason codes for SM-DP+/SM-DS errors
    

A typical error handler might look like this:

```java
int operationCode = intent.getIntExtra(
    EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_OPERATION_CODE, 0);
int errorCode = intent.getIntExtra(
    EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_ERROR_CODE, 0);
String smdxSubjectCode = intent.getStringExtra(
    EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_SMDX_SUBJECT_CODE);
String smdxReasonCode = intent.getStringExtra(
    EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_SMDX_REASON_CODE);

if (operationCode == OPERATION_DOWNLOAD && errorCode == ERROR_CARRIER_LOCKED) {
    // Device is carrier locked
} else if (operationCode == OPERATION_SMDX) {
    // SM-DP+ or SM-DS error — inspect smdxSubjectCode/smdxReasonCode
} else if (errorCode == ERROR_TIME_OUT) {
    // Network timeout — suggest retry
}
```

Some errors are **resolvable** — for example, when a confirmation code is required. In these cases, the callback returns `EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR`, and the carrier app can call `startResolutionActivity()` to trigger the LPA UI for user input.

[Roami](https://www.roamiapp.com/)'s platform handles the majority of these error conditions server-side before they ever reach the device — activation codes are validated, SM-DP+ addresses are pre-configured, and profile compatibility is checked before the LPA even initiates the download. This reduces the surface area for resolvable errors on the client side.

* * *

## Compatibility Considerations

Several practical issues affect eSIM API implementations across Android versions and device manufacturers.

**Android 13 and** `switchToSubscription`. There was a known issue where compiling an app targeting API level 33 caused `switchToSubscription` with `INVALID_SUBSCRIPTION_ID` to fail, even though the same code worked when targeting API level 32. This highlights the importance of testing against multiple target SDK versions.

**Multiple LPA instances**. If a device has more than one LPA app installed, the system selects the active LPA based on the intent filter priority declared in each app's manifest. The LPA with the highest priority wins. If no LPA is found, eUICC support is disabled entirely.

**Device feature detection**. Not all Android devices with eSIM hardware expose the same capabilities. Before attempting any eUICC operation, check:

*   `mgr.isEnabled()` — embedded subscriptions are enabled
    
*   `PackageManager.hasSystemFeature(FEATURE_TELEPHONY_EUICC)` — hardware supports eSIM
    
*   `mgr.getEid()` — the eUICC is ready and the app has carrier privileges to access it
    

* * *

## Summary: Which API for Which Use Case

|  | EuiccManager | EuiccCardManager |
| --- | --- | --- |
| **Target caller** | Carrier apps | LPA (system apps) |
| **Permission model** | Carrier privileges from profile metadata | System-level (must be LPA) |
| **Abstract level** | High — subscription management | Low — ES10x APDU commands |
| **Typical operations** | Download, switch, delete profiles | prepareDownload, loadBoundProfilePackage |
| **Android version** | Android 9+ | Android 9+ (system API) |
| **Best for** | Third-party carrier or travel apps | OEM LPA implementations |

The two APIs are not interchangeable — they serve different callers with different privilege models. For most app developers, `EuiccManager` is the only accessible path, and even then, carrier privileges are required. For LPA developers, `EuiccCardManager` is the bridge between the Android telephony framework and the eUICC chip's GSMA-compliant command set.

Understanding this layered architecture is the foundation for any serious Android eSIM integration.
