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() 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 get() = channels.toList() fun invalidate() { for (channel in channels) { channel.close() } channels.clear() seService?.shutdown() seService = null } }