144 lines
4.3 KiB
Kotlin
144 lines
4.3 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 im.angry.openeuicc.util.*
|
|
import kotlinx.coroutines.Dispatchers
|
|
import kotlinx.coroutines.runBlocking
|
|
import kotlinx.coroutines.sync.Mutex
|
|
import kotlinx.coroutines.sync.withLock
|
|
import kotlinx.coroutines.withContext
|
|
import java.lang.Exception
|
|
import kotlin.coroutines.resume
|
|
import kotlin.coroutines.suspendCoroutine
|
|
|
|
class EuiccChannelManager(private val context: Context) {
|
|
companion object {
|
|
const val TAG = "EuiccChannelManager"
|
|
}
|
|
|
|
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}",
|
|
tm.getImei(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
|
|
}
|
|
|
|
// Clean up channels left open in TelephonyManager
|
|
// due to a (potentially) forced restart
|
|
// This should be called every time the application is restarted
|
|
fun closeAllStaleChannels() {
|
|
for (card in tm.uiccCardsInfo) {
|
|
for (channel in 0 until 10) {
|
|
try {
|
|
tm.iccCloseLogicalChannelBySlot(card.slotIndex, channel)
|
|
} catch (_: Exception) {
|
|
// We do not care
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |