You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android Kotlin实现eSIM遇EMBEDDED_SUBSCRIPTION_RESULT_ERROR求助

eSIM安装应用问题解决方案

问题排查与修复

1. 卡在EMBEDDED_SUBSCRIPTION_RESULT_ERROR的问题

  • 核心问题:你在EMBEDDED_SUBSCRIPTION_RESULT_ERROR分支错误调用了handleResolvableError——该方法仅适用于可恢复错误(RESOLVABLE_ERROR),而ERROR属于不可恢复错误,应优先通过详细错误码定位根因。
  • 修复步骤:
    • 在onReceive中打印detailedCode,根据官方定义的详细错误码排查具体原因(如激活码无效、设备eSIM硬件不兼容、运营商配置缺失等)。
    • 移除ERROR分支中的handleResolvableError调用,改为输出详细日志辅助排查。
    • 确认激活码格式合规,且对应运营商支持当前设备的eSIM功能。

2. 自定义权限下无法触发onReceive的问题

  • 核心问题:动态注册广播时使用自定义权限,需确保权限在Manifest中正确声明,且权限校验逻辑符合系统要求。
  • 修复步骤:
    1. 在AndroidManifest.xml中添加权限声明:
      <permission
          android:name="com.example.testesimsdk.lpa.permission.BROADCAST"
          android:protectionLevel="signature" />
      
    2. 动态注册广播时,确保权限参数与Manifest一致,并适配Android 12+的PendingIntent标志:
      this.registerReceiver(
          broadcastReceiver,
          IntentFilter(ACTION_DOWNLOAD_SUBSCRIPTION),
          LPA_DECLARED_PERMISSION,
          null
      )
      
    3. 确认应用签名一致(signature级别权限仅允许同签名应用发送对应广播)。

3. 安装eSIM时传入自定义确认码的实现

  • 核心逻辑:当激活码需要确认码时,系统会返回EMBEDDED_SUBSCRIPTION_DETAILED_CODE_CONFIRMATION_CODE_REQUIRED详细错误码,此时需弹出输入框获取用户输入,重新构造带确认码的激活码后发起下载。
  • 实现步骤:
    1. 在onReceive中判断详细错误码,触发确认码输入流程。
    2. 获取确认码后,按格式拼接至原激活码(格式:原激活码,确认码),重新调用下载接口。

修正后的完整代码

package com.example.testesimsdk

import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import android.telephony.euicc.DownloadableSubscription
import android.telephony.euicc.EuiccManager
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AlertDialog

class MainActivity : AppCompatActivity(), View.OnClickListener {
    private val ACTIVATION_CODE = "LPA:1\$tl2.prod.ondemandconnectivity.com\$XWSAGDHOHV2VBGLN"
    private val Success = "Success"
    private val ACTION_DOWNLOAD_SUBSCRIPTION = "download_subscription"
    private val ACTION_DELETE_SUBSCRIPTION = "delete_subscription"
    private val LPA_DECLARED_PERMISSION = "com.example.testesimsdk.lpa.permission.BROADCAST"

    private var mgr: EuiccManager? = null
    private var broadcastReceiver: BroadcastReceiver? = null
    private lateinit var btn: Button

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Toast.makeText(applicationContext, ACTIVATION_CODE, Toast.LENGTH_LONG).show()
        implementEsim()
        btn = findViewById(R.id.button)
        btn.setOnClickListener(this)
    }

    private fun implementEsim() {
        setupEsim()
    }

    private fun initMgr() {
        if (mgr == null) {
            mgr = getSystemService(EUICC_SERVICE) as EuiccManager
        }
    }

    private fun isEsimSupported(): Boolean {
        initMgr()
        return mgr?.isEnabled ?: false
    }

    private fun setupEsim() {
        initMgr()
        if (isEsimSupported()) {
            broadcastReceiver = object : BroadcastReceiver() {
                override fun onReceive(context: Context, intent: Intent) {
                    if (ACTION_DOWNLOAD_SUBSCRIPTION != intent.action) {
                        Log.d("[MainActivity]", "Invalid intent action: ${intent.action}")
                        return
                    }

                    val resultCode = resultCode
                    val detailedCode = intent.getIntExtra(
                        EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,
                        0
                    )

                    when (resultCode) {
                        EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR -> {
                            Toast.makeText(context, "可恢复错误,详情码:$detailedCode", Toast.LENGTH_LONG).show()
                            Log.d("[MainActivity]", "RESOLVABLE_ERROR, detailedCode: $detailedCode")
                            handleResolvableError(intent, detailedCode)
                        }
                        EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK -> {
                            Toast.makeText(context, Success, Toast.LENGTH_LONG).show()
                            Log.d("[MainActivity]", "下载成功")
                        }
                        EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_ERROR -> {
                            Toast.makeText(context, "不可恢复错误,详情码:$detailedCode", Toast.LENGTH_LONG).show()
                            Log.d("[MainActivity]", "ERROR, detailedCode: $detailedCode")
                        }
                        else -> {
                            Log.d("[MainActivity]", "未知错误,resultCode: $resultCode")
                        }
                    }
                }
            }

            // 使用自定义权限注册广播
            this.registerReceiver(
                broadcastReceiver,
                IntentFilter(ACTION_DOWNLOAD_SUBSCRIPTION),
                LPA_DECLARED_PERMISSION,
                null
            )

            startDownloadSubscription(ACTIVATION_CODE)
        } else {
            Toast.makeText(this, "设备不支持eSIM", Toast.LENGTH_LONG).show()
        }
    }

    private fun startDownloadSubscription(activationCode: String) {
        val sub = DownloadableSubscription.forActivationCode(activationCode)
        val intent = Intent(ACTION_DOWNLOAD_SUBSCRIPTION)
        val callbackIntent = PendingIntent.getBroadcast(
            this, 0, intent,
            PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
        )
        mgr?.downloadSubscription(sub, true, callbackIntent)
    }

    private fun handleResolvableError(intent: Intent, detailedCode: Int) {
        when (detailedCode) {
            EuiccManager.EMBEDDED_SUBSCRIPTION_DETAILED_CODE_CONFIRMATION_CODE_REQUIRED -> {
                // 弹出对话框获取确认码
                showConfirmationCodeDialog()
            }
            else -> {
                // 其他可恢复错误,调用系统默认处理
                try {
                    val callbackIntent = PendingIntent.getBroadcast(
                        this, detailedCode,
                        Intent(ACTION_DOWNLOAD_SUBSCRIPTION),
                        PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
                    )
                    mgr?.startResolutionActivity(
                        this,
                        detailedCode,
                        intent,
                        callbackIntent
                    )
                } catch (e: Exception) {
                    Log.d("[MainActivity]", "处理可恢复错误失败:${e.localizedMessage}")
                }
            }
        }
    }

    private fun showConfirmationCodeDialog() {
        val builder = AlertDialog.Builder(this)
        builder.setTitle("请输入确认码")
        val input = android.widget.EditText(this)
        builder.setView(input)

        builder.setPositiveButton("确认") { _, _ ->
            val confirmationCode = input.text.toString().trim()
            if (confirmationCode.isNotEmpty()) {
                // 构造包含确认码的激活码
                val newActivationCode = "$ACTIVATION_CODE,$confirmationCode"
                startDownloadSubscription(newActivationCode)
            } else {
                Toast.makeText(this, "确认码不能为空", Toast.LENGTH_SHORT).show()
            }
        }
        builder.setNegativeButton("取消", null)
        builder.show()
    }

    private fun deleteSubs() {
        // 动态获取当前激活的eSIM订阅ID,避免硬编码
        mgr?.activeSubscriptionInfoList?.firstOrNull()?.let { subInfo ->
            val intent = Intent(ACTION_DELETE_SUBSCRIPTION)
            val callbackIntent = PendingIntent.getBroadcast(
                this, 0, intent,
                PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
            )
            mgr?.deleteSubscription(subInfo.subscriptionId, callbackIntent)
            Toast.makeText(this, "开始删除订阅", Toast.LENGTH_SHORT).show()
        } ?: Toast.makeText(this, "无可用eSIM订阅", Toast.LENGTH_SHORT).show()
    }

    override fun onClick(p0: View) {
        when (p0.id) {
            R.id.button -> deleteSubs()
        }
    }

    override fun onDestroy() {
        broadcastReceiver?.let { unregisterReceiver(it) }
        super.onDestroy()
    }
}

额外注意事项

  • 权限配置:需在Manifest中添加android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS权限(普通应用需通过系统授权流程获取,系统签名应用可直接声明)。
  • 激活码有效性:确保激活码未过期,且对应运营商支持当前设备所在地区的eSIM服务。
  • 版本适配:eSIM API仅在Android 8.0(API 26)及以上可用,需在Manifest中声明android:minSdkVersion="26"。

内容的提问来源于stack exchange,提问作者Tamir Yakov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 10:40:52