I have a telecommunication application (written in Jetpack Compose) which helps to install eSIM with just a single click.
I’ve created an EsimHandler to do this. Here you can see:
import android.app.Activity
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import android.telephony.TelephonyManager
import android.telephony.euicc.DownloadableSubscription
import android.telephony.euicc.EuiccManager
import android.util.Log
import androidx.annotation.RequiresApi
import kotlinx.coroutines.*
private const val ACTION_DOWNLOAD_SUBSCRIPTION = "download_subscription"
private const val TAG_ESIM = "TAG_ESIM"
class EsimHandler {
private lateinit var onEsimDownloadListener: OnEsimDownloadListener
private val receiver = object : BroadcastReceiver() {
@RequiresApi(Build.VERSION_CODES.P)
override fun onReceive(context: Context?, intent: Intent?) {
val resultCode = resultCode
when (resultCode) {
EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK -> {
/*Download profile was successful*/
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// eSim is active
onEsimDownloadListener.onResultOK()
} else {
// eSim is inactive due to the SDK does not support this API level
onEsimDownloadListener.onResultError()
}
}
EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR -> {
/*Download profile was not successful but resolvable*/
onEsimDownloadListener.onResultResolvableError()
val mgr = context?.getSystemService(Context.EUICC_SERVICE) as EuiccManager
handleResolvableError(context, intent!!, mgr)
}
else -> { /*Download profile was not successful*/
onEsimDownloadListener.onResultError()
}
}
}
}
fun init(param: OnEsimDownloadListener) {
this.onEsimDownloadListener = param
}
@OptIn(DelicateCoroutinesApi::class)
@RequiresApi(Build.VERSION_CODES.P)
fun downloadEsim(context: Context, code: String) {
/*
if (!checkCarrierPrivileges(context)) {
onEsimDownloadListener.onCheckCarrierPrivilegesFailure()
return
}
*/
val mgr = context.getSystemService(Context.EUICC_SERVICE) as EuiccManager
if (!mgr.isEnabled) {
onEsimDownloadListener.onMgrIsEnabledFailure()
return
}
// Download subscription asynchronously
val sub = DownloadableSubscription.forActivationCode(code)
val intent = Intent(ACTION_DOWNLOAD_SUBSCRIPTION)
val callbackIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT)
} else {
PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_MUTABLE)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.registerReceiver(receiver, IntentFilter(ACTION_DOWNLOAD_SUBSCRIPTION), Context.RECEIVER_NOT_EXPORTED)
} else {
context.registerReceiver(
receiver, IntentFilter(ACTION_DOWNLOAD_SUBSCRIPTION),
null, null
)
}
GlobalScope.launch {
withContext(Dispatchers.Main) {
try {
mgr.downloadSubscription(sub, true, callbackIntent)
} catch (e: Exception) {
onEsimDownloadListener.onResultError()
}
}
}
}
// Checks for carrier privileges on the device
private fun checkCarrierPrivileges(context: Context): Boolean {
val telephonyManager =
context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
val isCarrier = telephonyManager.hasCarrierPrivileges()
return if (isCarrier) {
Log.i(TAG_ESIM, "Ready with carrier privileges")
true
} else {
Log.i(TAG_ESIM, "No carrier privileges detected")
false
}
}
@RequiresApi(api = Build.VERSION_CODES.P)
private fun handleResolvableError(context: Context, intent: Intent, mgr: EuiccManager) {
try {
// Resolvable error, attempt to resolve it by a user action
val resolutionRequestCode = 3
val callbackIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
PendingIntent.getBroadcast(context, resolutionRequestCode, Intent(ACTION_DOWNLOAD_SUBSCRIPTION), PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT)
} else {
PendingIntent.getBroadcast(context, resolutionRequestCode, Intent(ACTION_DOWNLOAD_SUBSCRIPTION), PendingIntent.FLAG_MUTABLE)
}
mgr.startResolutionActivity(
context.activity(),
resolutionRequestCode,
intent,
callbackIntent
)
} catch (e: Exception) {
onEsimDownloadListener.onResultError()
Log.d(TAG_ESIM,
"EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR - Can't setup eSim due to Activity error "
+ e.localizedMessage
)
}
}
private fun Context.activity(): Activity? = when {
this is Activity -> this
else -> (this as? ContextWrapper)?.baseContext?.activity()
}
}
Simply, first I initialize my onEsimDownloadListener
in onCreate
of MainActivity
. Then, in related areas, I trigger downloadEsim
method via my viewmodel, then it registers a BroadcastReceiver that listens the result code of this event. When I got a result code, I listen it via my onEsimDownloadListener
I’m %100 sure that my eSIM profile has carrier privilege because I can install eSIM on my Google Pixel 7a device with Android 13 perfectly. But the problem is, I can’t install same eSIM profile to my Samsung Galaxy S23 with Android 14.
While I’m trying to install eSIM profile to Google Pixel 7a device with Android 13, after I trigger downloadEsim method, it joins resolvable error case.
Then I see those logs:
EuiccController com.android.phone I isCompatChangeEnabled changeId: SOME_ID_HERE_BLABLA changeEnabled: true
EuiccController com.android.phone D downloadSubscription cardId: 0 switchAfterDownload: true portIndex: 0 forceDeactivateSim: false callingPackage: com.myapp.myapp isConsentNeededToResolvePortIndex: false shouldResolvePortIndex:true
EuiccController com.android.phone I Caller can't manage subscription on target SIM or User consent is required for resolving port index. Ask user's consent first
then I see this on my phone:
I click Yes, then I see those logs:
EuiccController com.android.phone I continueOperation portIndex: 0 usePortIndex: true
EuiccController com.android.phone D downloadSubscriptionPrivilegedCheckMetadata cardId: 0 switchAfterDownload: true portIndex: 0 forceDeactivateSim: true
5 seconds later, I see this log:
EuiccController com.android.phone I Calling package has carrier privilege to this profile
Then after I wait 5 minutes, my eSIM profile is downloaded and installed successfully.
While I’m trying to install eSIM profile to Samsung Galaxy S23 with Android 14, after I trigger downloadEsim method, I see those logs:
EuiccController com.android.phone I isCompatChangeEnabled changeId: SOME_ID_HERE_BLABLA changeEnabled: true
EuiccController com.android.phone I No UiccSlotInfo found for cardId: 0
EuiccController com.android.phone D Switch to inactive slot, return default port index. slotIndex: -1
EuiccController com.android.phone D downloadSubscription cardId: 0 switchAfterDownload: true portIndex: 0 forceDeactivateSim: false callingPackage: com.myapp.myapp isConsentNeededToResolvePortIndex: false shouldResolvePortIndex:true
EuiccController com.android.phone D euiccController phoneId: 1
EuiccController com.android.phone E Calling package doesn't have carrier privilege to this profile
EuiccController com.android.phone I The target SIM is not an eUICC.
EuiccController com.android.phone I Caller can't manage subscription on target SIM or User consent is required for resolving port index. Ask user's consent first
EuiccController com.android.phone I The target SIM is not an eUICC.
EuiccController com.android.phone I euiccController add EuiccService.NO_PRIVILEGED
Then nothing happens. I can’t receive nothing from my receiver. My onEsimDownloadListener
doesn’t observe nothing.
I tried my chance with this official link, but nothing happens.
Those lines are already added in my Manifest file:
<!--
Needed for Single Click eSIM Activation
-->
<uses-permission android:name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"
tools:ignore="ProtectedPermissions" />
<service android:name=".SampleCarrierConfigService"
android:exported="true"
android:permission="android.permission.BIND_CARRIER_SERVICES">
<intent-filter>
<action android:name="android.service.carrier.CarrierService"/></intent-filter>
</service>
What am I missing ? How can I solve this issue on my other device ?