Implementación de Android eSIM-LPA basado en Android13

Implementación de eSIM-Android-LPA basada en Android 13

Todas las especificaciones internacionales relacionadas con ESIM se definen en: GSMA Spec (SGP) - Especificaciones de IoT y consumidores de eSIM

Las regulaciones nacionales relacionadas con EID se encuentran en: Asociación de la Industria de Terminales de Telecomunicaciones-Regulaciones de Implementación de Gestión de EID

La documentación oficial de Android está en: implementación de eSIM

Saber antes del desarrollo

¿Qué es la eSIM?

La tecnología SIM integrada (también conocida como eSIM o eUICC ) permite a los usuarios de dispositivos móviles descargar perfiles de operador y activar servicios de operador sin una tarjeta SIM física . La tecnología es una especificación global impulsada por GSMA que permite el aprovisionamiento remoto de SIM (RSP) en cualquier dispositivo móvil . A partir de Android 9 , el marco de Android proporciona una API estándar para acceder a eSIM y administrar perfiles de suscripción en eSIM. Con estas API de eUICC , terceros pueden desarrollar sus propias aplicaciones de operador y Asistente de perfil local (LPA) en dispositivos Android habilitados para eSIM .

  1. Para acceder a la API oculta del sistema, primero debe compilar framework.jar y telephony-common.jar del compileSdkVersion correspondiente para compilarlo y verificarlo durante el empaquetado LPA .

  2. En segundo lugar, la aplicación debe existir como una aplicación del sistema. Después de la autofirma, use la firma de la aplicación del sistema, luego instálela en system/priv-app/ y configure la aplicación necesaria en etc/permissions/privapp-permissions-platform. Archivo .xml . Permisos especiales, como:

<privapp-permissions package="com.xxx.lpa">
    <permission name="android.permission.INTERNET"/>
    <permission name="android.permission.READ_PHONE_STATE"/>
    <permission name="android.permission.MODIFY_PHONE_STATE"/>
    <permission name="android.permission.BIND_EUICC_SERVICE"/>
    <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
    <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
    <permission name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"/>
</privapp-permissions>
  1. De manera similar, si el sistema utiliza funciones de hardware especiales como Euicc , las funciones de hardware que deben habilitarse deben configurarse en el archivo etc/permissions/android.hardware.telphony.euicc.xml .
<permissions>
    <feature name="android.hardware.telephony.euicc" />
    <feature name="android.hardware.telephony.radio.access" />
	<feature name="android.hardware.telephony.subscription" />
</permissions>
  1. Finalmente, use EuiccManager.isEnabled para verificar y obtener el resultado verdadero , lo que indica que puede usar las API relacionadas con eSIM para el desarrollo normalmente.

Proceso de instalación de la aplicación del sistema:

1. adb root
2. adb remount
3. adb push lpa.apk system/priv-app/lpa/lpa.apk 推送apk到系统目录
4. adb shell am restart/ adb shell reboot 重启am或者系统,让系统自动检查并安装

involucrando sustantivos

clase de identificación

  • AID hexadecimal de 32 dígitos

    com.android.telefonía interna.uicc.euicc.EuiccCard

    Ejemplo: A0000005591010FFFFFFFF8900000100

  • decimal de 32 bits

    Ejemplo: 89033023001211360000000007226088

  • cardId hexadecimal de 20 dígitos

    La tarjeta eSIM es eid, la tarjeta SIM normal es iccid

  • ICCID hexadecimal de 20 dígitos

Identidad de la tarjeta de circuito integrado Código de identificación de la tarjeta de circuito integrado (solidificado en la tarjeta SIM del teléfono móvil) ICCID es el número de identificación único de la tarjeta IC, que consta de un total de 20 dígitos

样例1:898602F30918A0009913
样例2:89860922780011058162
  • ID de ranura de tarjeta SlotId/SlotIndex

    0 (ranura para tarjeta 1), 1 (ranura para tarjeta 2)

otro

  • APDU

    APDU se define en la especificación ISO/IEC 7816-4 y es un protocolo utilizado para interactuar ( envío/respuesta ) con la tarjeta SIM , y se presenta específicamente como una serie de instrucciones codificadas en hexadecimal .

Ejemplo:

80E2910006BF3E035C015A(发送)、BF3E125A10890860302022000000220000153565489000(响应)

Euicc en Android 13

Las API relacionadas con Euicc han estado disponibles desde Android 9. 10, 11, 12 y 13 han agregado algunas extensiones o han marcado algunas como obsoletas, como agregar funciones de múltiples tarjetas y admitir la interacción con las aplicaciones del operador...

Involucrando categorías de entrada clave:

  • android.service.euicc.EuiccService -> EuiccServiceImpl debe ser implementado por nosotros mismos
  • android.telephony.euicc.EuiccManager -> EuiccController -> EuiccConnector

Variables importantes guardadas en EuiccManager

private final EuiccConnector mConnector;
private final SubscriptionManager mSubscriptionManager;
private final TelephonyManager mTelephonyManager;
private final AppOpsManager mAppOpsManager;
private final PackageManager mPackageManager;
  • android.telephony.euicc.EuiccCardManager -> EuiccCardController -> EuiccCard/UiccController/SubscriptionController

  • android.telephony.TelephonyManager -> Administrador de suscripciones

  • android.telephony.SubscriptionManager -> Controlador de suscripción

Involucrando clases subyacentes clave:

com.android.internal.telphony.euicc.EuiccConnector (conecta EuiccManager y EuiccService)

En el paquete com.android.internal.telephony.uicc:

UiccController(直接操作卡槽)
euicc.EuiccCard(EuiccController 中部分Card方法的实现)
euicc.apdu.ApduSender(EuiccCard 中进行 APDU 命令发送)
 ApduSender错误举例:android.telephony.IccOpenLogicalChannelResponse(Response to the TelephonyManager.iccOpenLogicalChannel command.)
euicc.Tags(EuiccCard 中使用于 APDU 命令发送的 ASN.1 tag 定义)
euicc.EuiccCardErrorException(Card相关方法产生的异常定义)
euicc.apdu.ApduException(APDU 命令产生的异常定义)

en conclusión:

Algunos de los métodos de la clase EuiccManager se implementan a través de varios xxxManagers (finalmente envían comandos APDU), y algunos se llaman a EuiccService a través de la clase EuiccConnector y deben ser implementados por los propios desarrolladores.

Pila de llamadas aproximada:

xxxManager -> xxxController -> (EuiccConnector -> EuiccService/ EuiccCard -> ApduSender)

Implementación de EuiccService

Desde la perspectiva de Lui, hay cinco métodos principales que deben implementarse:

  1. onDownloadSubcription (descargar archivo de suscripción)
  2. onGetEuiccProfileInfoList (obtener toda la información del perfil)
  3. onSwitchToSubscription (cambiar perfil)
  4. onUpdateSubscriptionNickname (editar el campo de apodo de un perfil)
  5. onDeleteSubscription (eliminar un perfil)

Pero, de hecho, el método más básico debe implementarse internamente: onGetEid (get eid) . Este es diferente de los cinco métodos anteriores y debe distinguirse. Los siguientes métodos no están ordenados. A excepción de getEid, son todo de fácil a difícil.

premisa:

入口类:EuiccCardManager
除了 getEid 每个方法均回调为:
  EuiccCardManager.ResultCallback(resultCode, result)
resultCode:响应码,0为成功(EuiccService.RESULT_OK),其他负数值为失败,失败需处理返回异常
result:APDU响应指令

Implementación de onGetEid:

Función de entrada:

public String EuiccManager.getEid() {
	return getIEuiccController().getEid(...)
}

La capa EuiccController pasa EuiccConnector y finalmente llama al método EuiccService.onGetEid para implementarlo. ¡Si no tienes cuidado, entrarás en el pozo de un bucle infinito!

La implementación es muy simple. Las versiones de Android 10 y superiores usan el nuevo método getUiccCardsInfo . Las siguientes versiones usan el antiguo getUiccSlotsInfo para obtener la lista de información de todas las tarjetas. Dado que solo se puede habilitar una función de ranura para tarjetas al mismo tiempo, simplemente busque la uno que no está vacío. De hecho, existe una implementación pública de getEid() en la clase EuiccCard , pero el sistema no proporciona un método de entrada de nivel superior.

override fun onGetEid(slotId: Int): String {
    
    
    val eid =
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
            tm.uiccCardsInfo.find {
    
    
                it.isEuicc && !it.eid.isNullOrEmpty()
            }?.eid ?: ""
        } else {
    
    
            tm.uiccSlotsInfo.find {
    
    
                it.isEuicc && !it.cardId.isNullOrEmpty()
            }?.cardId ?: ""
        }
XLog.d("Service onGetEid slotId: $slotId, eid:$eid")
return eid
}

Inserte una sola tarjeta eSIM en la ranura para tarjetas duales y compare las dos Información

TelephonyManager.getUiccSlotInfo():

UiccSlotInfo (mIsActive=true, mIsEuicc=true, mCardId=89033023001211360000000007226088, cardState=2, phoneId=0, mIsExtendedApduSupported=false, mIsRemovable=true)

UiccSlotInfo (mIsActive=true, mIsEuicc=false, mCardId=, cardState=1, phoneId=1, mIsExtendedApduSupported=false, mIsRemovable=true)

TelephonyManager.getUiccCardInfo():

UiccCardInfo (mIsEuicc=true, mCardId=5, mEid=89033023001211360000000007226088, mIccId=8986100000000000000022, mSlotIndex=0, mIsRemovable=true)

UiccCardInfo (mIsEuicc=false, mCardId=-2, mEid=null, mIccId=, mSlotIndex=1, mIsRemovable=true)

Implementación de onGetEuiccProfileInfoList

Funciones de llamada:

public void EuiccCardManager.requestAllProfiles(
	String cardId, 
    @CallbackExecutor Executor executor, 
	ResultCallback<EuiccProfileInfo> callback
) {
	getIEuiccCardController().getAllProfiles(...)
	...
}

Ejemplo de ProfileInfoList: finalmente, debe mostrarse en Lui para que el usuario pueda operarlo

[
  {
    
    
    "iccid": "898602F30918A0009911",
    "nickname": "CM",
    "profileName": "",
    "providerName": "",
    "state": 1
  },
  {
    
    
    "iccid": "89860922780011058161",
    "nickname": "CU",
    "profileName": "",
    "providerName": "",
    "state": 0
  },
  {
    
    
    "iccid": "89861122215036305451",
    "nickname": "CT",
    "profileName": "",
    "providerName": "",
    "state": 0
  }
]

Implementación de onSwitchToSubscription

Funciones de llamada:

public void EuiccCardManager.switchToProfile(
	String cardId, String iccid, boolean refresh,
    @CallbackExecutor Executor executor, 
	ResultCallback<EuiccProfileInfo> callback
) {
	getIEuiccCardController().switchToProfile(...)
	...
}

Error 1 encontrado:

逻辑通道被占用
switchToProfile callback onException: 
  com.android.internal.telephony.uicc.euicc.EuiccCardException: Cannot send APDU.
  ApduException: The logical channel is in use. (apduStatus=0)

Razón:

  1. Cambie inmediatamente después de descargar su suscripción
  2. Varios subprocesos llaman simultáneamente a la misma interfaz de la EuiccCard subyacente (cuando un canal lógico está habilitado y ocupado, debe liberarse antes de poder reutilizarse)

Cómo solucionar problemas o resolver:

  1. Espere unos segundos después de la descarga y luego llame al interruptor
  2. Verifique si hay llamadas simultáneas a la misma interfaz en Lui

Implementación onUpdateSubscriptionNickname

Funciones de llamada:

public void EuiccCardManager.setNickname(
	String cardId, String iccid, String nickname,
    @CallbackExecutor Executor executor, 
	ResultCallback<EuiccProfileInfo> callback
) {
	getIEuiccCardController().setNickname(...)
	...
}

Implementación de onDeleteSubscription

Funciones de llamada:

public void EuiccCardManager.deleteProfile(
	String cardId, String iccid,
    @CallbackExecutor Executor executor, 
	ResultCallback<EuiccProfileInfo> callback
) {
	getIEuiccCardController().deleteProfile(...)
	...
}

Descargar implementación de suscripción:

1. Proceso de descarga

El proceso de descarga es más complicado.

La aplicación del operador abre el LPA para iniciar la descarga y lleva el Código de activación (el código de activación generalmente se obtiene escaneando el código QR). EuiccManager.downloadSubscription en Lui es la función de entrada. El parámetro PendingIntent contiene la acción: ACTION_DOWNLOAD . Al mismo Al mismo tiempo, se registra un BroadcastReceiver para recibir el resultado de la descarga. Durante la descarga, es posible que tengas que ir a la APP del operador para confirmar el código de descarga.

Finalmente, es necesario implementar el método EuiccService.onDownloadSubscription:

override fun onDownloadSubscription(
    slotId: Int,
    subscription: DownloadableSubscription?,
    switchAfterDownload: Boolean,
    forceDeactivateSim: Boolean,
    resolvedBundle: Bundle?,
): DownloadSubscriptionResult {
	return null
}

2. Descargue los pasos de implementación

Para todas las llamadas a la interfaz, el encabezado contiene el campo de estado y la respuesta contiene la información de verificación.

ejemplo de encabezado:

{
    
    
  "header": {
    
    
    "functionExecutionStatus": {
    
    
      "status": "Executed-Success"
    }
  },
  "transactionId": "FD913D91F79702FBF27DE2B4ABBD468A"
}

muestra de respuesta:

{
    
    "pendingNotification":"vzeBrr8naIAQzS/mWM/RNgcHY2VAyc6yGr8vJoABB4ECB4AMEWRwcGx1cy5jY3NtZWMuY29tWgqYaAEAAAAAAAAzBgorBgEEAYOOA2UCoh+gHU8QoAAABVkQEP+JAAARAAQJMAegBTADgAEAXzdA0iN1N/yZEKdsbUub2PXOW9eT5NJi97ajNnu44rMTVimQnDzwvk4+hvsQFHH293xn7SUnfZM6gJswqaiF9gpONB=="}

1. getEid es un requisito previo y se utiliza al operar la tarjeta.

val cardId = onGetEid(slotId)

2. Analice y verifique el código de activación.

Obtenga el código de activación, si está vacío o es ilegal, se debe interrumpir la operación y se devolverá un error:

val activationCode = subscription?.encodedActivationCode
// 激活码样例:1$dpplus.xxx.com:8445/xxfreexx-dp-service$2TSU5-NKVPM-MIS64-9CLYD-KD411

val args = ac.split('$')
val smdpAddress = "https://${args[1]}/"
val matchingId = args[2] // 服务端验证使用

3. Inicialización, verificación bidireccional, uso del código de activación para SM-DP+ para obtener información de suscripción

Proceso de llamada de interfaz:

1. initiateAuthentication
2. authenticateClient
3. getBoundProfilePackage
4. handleNotification

Etiquetas importantes para la respuesta de la interfaz:

1. euiccChallenge 字段
2. authenticateServerResponse 字段
3. prepareDownloadResponse 字段
4. pendingNotification 字段

Si todo va bien y sin problemas, cada llamada anterior corresponde a una respuesta, lo que indica que el proceso de descarga se ha completado.


El siguiente es un proceso API completo con causas y consecuencias:

  • API: EuiccCardManager.onGetEuiccChallenge
  • API: EuiccCardManager.onGetEuiccInfo1

1. iniciar llamada a la interfaz de autenticación

Respuesta de euiccChallenge , retorno (euiccChallenge, euiccInfo1, SM-DP+dirección del servidor)

// SM-DP+服务连接,初始化验证
val initAuthResp = es9.initiateAuthentication(
    InitiateAuthentication.Request(
        euiccChallenge.toBase64(),
        euiccInfo1.toBase64(),
        smdpAddress
    )
).execute().body()
  • API: EuiccCardManager.onAuthenticateServer
val serverSigned1 = initAuthResp.serverSigned1.base64ToBytes()
val serverSignature1 = initAuthResp.serverSignature1.base64ToBytes()
val ciPkIdToBeUsed = initAuthResp.euiccCiPKIdToBeUsed.base64ToBytes()
val serverCertificate = initAuthResp.serverCertificate.base64ToBytes()

euiccCardManager.authenticateServer(cardId,
    matchingId,
    serverSigned1,
    serverSignature1,
    ciPkIdToBeUsed,
    serverCertificate,
    AsyncTask.THREAD_POOL_EXECUTOR
) {
    
     resultCode, result ->
	...
}

2. llamada a la interfaz authenticateClient

respuesta authenticateServerResponse , función de retornoExecutionStatus.status=Ejecutado-Éxito Continuar

val authClientResp =
    es9.authenticateClient(AuthenticateClient.Request(initAuthResp.transactionId, authServerResp.toBase64()))
        .execute().body()
  • API: EuiccCardManager.onPrepareDownload
euiccCardManager.prepareDownload(
    cardId,
    if (subscription.getEncodedActivationCode().endsWith("\$1")) {
    
    
        sha256(sha256(subscription.confirmationCode.toByteArray()) + authClientResp.transactionId.hexStringToBytes())
    } else {
    
    
        null
    },
    authClientResp.smdpSigned2.base64ToBytes(),
    authClientResp.smdpSignature2.base64ToBytes(),
    authClientResp.smdpCertificate.base64ToBytes(),
    AsyncTask.THREAD_POOL_EXECUTOR
) {
    
     resultCode, result ->
	...
}

3. Llamada a la interfaz getBoundProfilePackage

prepararDescargarRespuesta respuesta

 val getBoundProfilePackageResp = es9.getBoundProfilePackage(
     GetBoundProfilePackage.Request(
         authClientResp.transactionId,
         prepareDownloadResp.toBase64()
     )
 ).execute().body()
  • API: EuiccCardManager.onLoadBoundProfilePackage
euiccCardManager.loadBoundProfilePackage(cardId, bpp, AsyncTask.THREAD_POOL_EXECUTOR) {
    
     resultCode, result ->
	...
}

4. manejar llamada a la interfaz de notificación

pendiente de respuesta de notificación

es9.handleNotification(HandleNotification.Request(installationResult.toBase64()))
    .execute().body()

4. Determine si necesita descargar y cambiar de perfil y ejecutarlo si es necesario.

Error encontrado 1: llamadas simultáneas de subprocesos múltiples o cambio de perfil inmediatamente después de la descarga. Verifique la cadena de llamadas y retrase el cambio intermedio después de la descarga.

E  getProfile in switchToProfile callback onException: 
 com.android.internal.telephony.uicc.euicc.EuiccCardException: Cannot parse response: BF2D03810101
 at com.android.internal.telephony.uicc.euicc.EuiccCard$2.onResult(EuiccCard.java:1251)
 at com.android.internal.telephony.uicc.euicc.EuiccCard$2.onResult(EuiccCard.java:1242)
 at com.android.internal.telephony.uicc.euicc.apdu.ApduSender$4.onResult(ApduSender.java:275)
 at com.android.internal.telephony.uicc.euicc.apdu.ApduSender$4.onResult(ApduSender.java:266)
 at com.android.internal.telephony.uicc.euicc.async.AsyncMessageInvocation.handleMessage(AsyncMessageInvocation.java:57)
 at android.os.Handler.dispatchMessage(Handler.java:102)
 at android.os.Looper.loop(Looper.java:223)
 at android.app.ActivityThread.main(ActivityThread.java:7822)
 at java.lang.reflect.Method.invoke(Native Method)
 at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
 at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:952)

Caused by: com.android.internal.telephony.uicc.asn1.TagNotFoundException: null (tag=160)
 at com.android.internal.telephony.uicc.asn1.Asn1Node.getChild(Asn1Node.java:330)
 at com.android.internal.telephony.uicc.euicc.EuiccCard.lambda$getProfile$5(EuiccCard.java:288)
 at com.android.internal.telephony.uicc.euicc.-$$Lambda$EuiccCard$TTvsStUIyUFrPpvGTlsjBCy3NyM.handleResult(Unknown Source:0)
 at com.android.internal.telephony.uicc.euicc.EuiccCard$2.onResult(EuiccCard.java:1246)
 at com.android.internal.telephony.uicc.euicc.EuiccCard$2.onResult(EuiccCard.java:1242) 
 at com.android.internal.telephony.uicc.euicc.apdu.ApduSender$4.onResult(ApduSender.java:275) 
 at com.android.internal.telephony.uicc.euicc.apdu.ApduSender$4.onResult(ApduSender.java:266) 
 at com.android.internal.telephony.uicc.euicc.async.AsyncMessageInvocation.handleMessage(AsyncMessageInvocation.java:57) 
 ...
 at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:952) 

Error 2 encontrado:
debido a que el eid es demasiado largo, el campo cardId: Int pasado en la clase de entidad devuelta por onDownloadSubscription es demasiado largo (no es necesario pasar este campo y no se usa internamente)

java.lang.NumberFormatException: For input string: "89033023001211360000000007226077"
 at java.lang.Integer.parseInt(Integer.java:618)
 at java.lang.Integer.parseInt(Integer.java:650)
 at com.xxx.lpa.service.EuiccService.onDownloadSubscription(EuiccService.kt:193)
 at android.service.euicc.EuiccService$IEuiccServiceWrapper$1.run(EuiccService.java:669)
 at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
 at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
 at java.lang.Thread.run(Thread.java:923)

El proceso de descarga finaliza

Supongo que te gusta

Origin blog.csdn.net/qq_39420519/article/details/127903469
Recomendado
Clasificación