OpenEUICC/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt
Peter Cai 5aed27513f
All checks were successful
/ build-debug (push) Successful in 3m56s
lpac-jni: Uprev lpac
2024-02-24 15:53:58 -05:00

139 lines
4.9 KiB
Kotlin

package net.typeblog.lpac_jni.impl
import android.util.Log
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import net.typeblog.lpac_jni.LpacJni
import net.typeblog.lpac_jni.ApduInterface
import net.typeblog.lpac_jni.EuiccInfo2
import net.typeblog.lpac_jni.HttpInterface
import net.typeblog.lpac_jni.LocalProfileAssistant
import net.typeblog.lpac_jni.LocalProfileInfo
import net.typeblog.lpac_jni.LocalProfileNotification
import net.typeblog.lpac_jni.ProfileDownloadCallback
class LocalProfileAssistantImpl(
private val apduInterface: ApduInterface,
private val httpInterface: HttpInterface
): LocalProfileAssistant {
companion object {
private const val TAG = "LocalProfileAssistantImpl"
}
private var contextHandle: Long = LpacJni.createContext(apduInterface, httpInterface)
init {
if (LpacJni.euiccInit(contextHandle) < 0) {
throw IllegalArgumentException("Failed to initialize LPA")
}
val pkids = euiccInfo2?.euiccCiPKIdListForVerification ?: arrayOf()
httpInterface.usePublicKeyIds(pkids)
}
private fun tryReconnect(timeoutMillis: Long) = runBlocking {
withTimeout(timeoutMillis) {
try {
LpacJni.euiccFini(contextHandle)
LpacJni.destroyContext(contextHandle)
contextHandle = -1
} catch (e: Exception) {
// Ignored
}
while (true) {
try {
apduInterface.disconnect()
} catch (e: Exception) {
// Ignored
}
try {
apduInterface.connect()
contextHandle = LpacJni.createContext(apduInterface, httpInterface)
check(LpacJni.euiccInit(contextHandle) >= 0) { "Reconnect attempt failed" }
// Validate that we can actually use the APDU channel by trying to read eID and profiles
check(valid) { "Reconnected channel is invalid" }
break
} catch (e: Exception) {
e.printStackTrace()
if (contextHandle != -1L) {
try {
LpacJni.euiccFini(contextHandle)
LpacJni.destroyContext(contextHandle)
contextHandle = -1
} catch (e: Exception) {
// Ignored
}
}
// continue retrying
delay(1000)
}
}
}
}
override val profiles: List<LocalProfileInfo>
get() = LpacJni.es10cGetProfilesInfo(contextHandle)!!.asList()
override val notifications: List<LocalProfileNotification>
get() =
(LpacJni.es10bListNotification(contextHandle) ?: arrayOf())
.sortedBy { it.seqNumber }.reversed()
override val eID: String
get() = LpacJni.es10cGetEid(contextHandle)!!
override val euiccInfo2: EuiccInfo2?
get() = LpacJni.es10cexGetEuiccInfo2(contextHandle)
override fun enableProfile(iccid: String, reconnectTimeout: Long): Boolean {
val res = LpacJni.es10cEnableProfile(contextHandle, iccid) == 0
if (reconnectTimeout > 0) {
try {
tryReconnect(reconnectTimeout)
} catch (e: Exception) {
return false
}
}
return res
}
override fun disableProfile(iccid: String, reconnectTimeout: Long): Boolean {
val res = LpacJni.es10cDisableProfile(contextHandle, iccid) == 0
if (reconnectTimeout > 0) {
try {
tryReconnect(reconnectTimeout)
} catch (e: Exception) {
return false
}
}
return res
}
override fun deleteProfile(iccid: String): Boolean {
return LpacJni.es10cDeleteProfile(contextHandle, iccid) == 0
}
override fun downloadProfile(smdp: String, matchingId: String?, imei: String?,
confirmationCode: String?, callback: ProfileDownloadCallback): Boolean {
return LpacJni.downloadProfile(contextHandle, smdp, matchingId, imei, confirmationCode, callback) == 0
}
override fun deleteNotification(seqNumber: Long): Boolean =
LpacJni.es10bDeleteNotification(contextHandle, seqNumber) == 0
override fun handleNotification(seqNumber: Long): Boolean =
LpacJni.handleNotification(contextHandle, seqNumber).also {
Log.d(TAG, "handleNotification $seqNumber = $it")
} == 0
override fun setNickname(iccid: String, nickname: String): Boolean {
return LpacJni.es10cSetNickname(contextHandle, iccid, nickname) == 0
}
override fun close() {
LpacJni.euiccFini(contextHandle)
LpacJni.destroyContext(contextHandle)
}
}