OpenEUICC/app/src/main/java/im/angry/openeuicc/core/EuiccChannelManager.kt
Peter Cai c3f4770108 Execute SEService initialization on the handler thread
This (hopefully) eliminates a race condition where the connected listenr
is called before the service variable is assigned.
2022-05-12 17:09:58 -04:00

127 lines
3.7 KiB
Kotlin

package im.angry.openeuicc.core
import android.content.Context
import android.os.Handler
import android.os.HandlerThread
import android.se.omapi.SEService
import android.telephony.UiccCardInfo
import android.util.Log
import im.angry.openeuicc.OpenEuiccApplication
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
class EuiccChannelManager(private val context: Context) {
companion object {
const val TAG = "EuiccChannelManager"
const val MAX_SIMS = 3
}
private val channels = mutableListOf<EuiccChannel>()
private var seService: SEService? = null
private val lock = Mutex()
private val tm by lazy {
(context.applicationContext as OpenEuiccApplication).telephonyManager
}
private val handler = Handler(HandlerThread("EuiccChannelManager").also { it.start() }.looper)
private suspend fun connectSEService(): SEService = suspendCoroutine { cont ->
handler.post {
var service: SEService? = null
service = SEService(context, { handler.post(it) }) {
cont.resume(service!!)
}
}
}
private suspend fun ensureSEService() {
if (seService == null) {
seService = connectSEService()
}
}
private suspend fun tryOpenEuiccChannel(uiccInfo: UiccCardInfo): EuiccChannel? {
lock.withLock {
ensureSEService()
val existing = channels.find { it.slotId == uiccInfo.slotIndex }
if (existing != null) {
if (existing.valid) {
return existing
} else {
existing.close()
channels.remove(existing)
}
}
val channelInfo = EuiccChannelInfo(
uiccInfo.slotIndex,
uiccInfo.cardId,
"SIM ${uiccInfo.slotIndex}",
uiccInfo.isRemovable
)
var euiccChannel: EuiccChannel? = null
if (uiccInfo.isEuicc && !uiccInfo.isRemovable) {
Log.d(TAG, "Using TelephonyManager for slot ${uiccInfo.slotIndex}")
// TODO: On Tiramisu, we should also connect all available "ports" for MEP support
euiccChannel = TelephonyManagerChannel.tryConnect(tm, channelInfo)
}
if (euiccChannel == null) {
euiccChannel = OmapiChannel.tryConnect(seService!!, channelInfo)
}
if (euiccChannel != null) {
channels.add(euiccChannel)
}
return euiccChannel
}
}
private suspend fun findEuiccChannelBySlot(slotId: Int): EuiccChannel? {
return tm.uiccCardsInfo.find { it.slotIndex == slotId }?.let {
tryOpenEuiccChannel(it)
}
}
fun findEuiccChannelBySlotBlocking(slotId: Int): EuiccChannel? = runBlocking {
withContext(Dispatchers.IO) {
findEuiccChannelBySlot(slotId)
}
}
suspend fun enumerateEuiccChannels() {
withContext(Dispatchers.IO) {
ensureSEService()
for (uiccInfo in tm.uiccCardsInfo) {
if (tryOpenEuiccChannel(uiccInfo) != null) {
Log.d(TAG, "Found eUICC on slot ${uiccInfo.slotIndex}")
}
}
}
}
val knownChannels: List<EuiccChannel>
get() = channels.toList()
fun invalidate() {
for (channel in channels) {
channel.close()
}
channels.clear()
seService?.shutdown()
seService = null
}
}