refactor: make EuiccChannel an interface instead of data class

This commit is contained in:
Peter Cai 2022-05-11 18:46:33 -04:00
parent 058b01533d
commit 4840236e23
6 changed files with 128 additions and 105 deletions

View file

@ -2,15 +2,24 @@ package im.angry.openeuicc.core
import com.truphone.lpa.LocalProfileAssistant import com.truphone.lpa.LocalProfileAssistant
interface EuiccChannelStateManager { // A custom type to avoid compatibility issues with UiccCardInfo / UiccPortInfo
val valid: Boolean data class EuiccChannelInfo(
fun destroy()
}
data class EuiccChannel(
val slotId: Int, val slotId: Int,
val cardId: Int, val cardId: Int,
val name: String, val name: String,
val lpa: LocalProfileAssistant, val removable: Boolean,
val stateManager: EuiccChannelStateManager )
)
abstract class EuiccChannel(
info: EuiccChannelInfo
) {
val slotId = info.slotId
val cardId = info.cardId
val name = info.name
val removable = info.removable
abstract val lpa: LocalProfileAssistant
abstract val valid: Boolean
abstract fun close()
}

View file

@ -4,10 +4,7 @@ import android.content.Context
import android.os.Handler import android.os.Handler
import android.os.HandlerThread import android.os.HandlerThread
import android.se.omapi.SEService import android.se.omapi.SEService
import android.telephony.TelephonyManager
import android.util.Log import android.util.Log
import com.truphone.lpa.ApduChannel
import com.truphone.lpa.impl.LocalProfileAssistantImpl
import im.angry.openeuicc.OpenEuiccApplication import im.angry.openeuicc.OpenEuiccApplication
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@ -48,46 +45,42 @@ class EuiccChannelManager(private val context: Context) {
ensureSEService() ensureSEService()
val existing = channels.find { it.slotId == slotId } val existing = channels.find { it.slotId == slotId }
if (existing != null) { if (existing != null) {
if (existing.stateManager.valid) { if (existing.valid) {
return existing return existing
} else { } else {
existing.stateManager.destroy() existing.close()
channels.remove(existing) channels.remove(existing)
} }
} }
val (shouldTryTelephonyManager, cardId) = val cardInfo = tm.uiccCardsInfo.find { it.slotIndex == slotId } ?: return null
tm.uiccCardsInfo.find { it.slotIndex == slotId }?.let {
Pair(it.isEuicc && !it.isRemovable, it.cardId)
} ?: Pair(false, 0)
var apduChannel: ApduChannel? = null val channelInfo = EuiccChannelInfo(
var stateManager: EuiccChannelStateManager? = null slotId, cardInfo.cardId, "SIM $slotId", cardInfo.isRemovable
)
val (shouldTryTelephonyManager, cardId) =
cardInfo.let {
Pair(it.isEuicc && !it.isRemovable, it.cardId)
}
var euiccChannel: EuiccChannel? = null
if (shouldTryTelephonyManager) { if (shouldTryTelephonyManager) {
Log.d(TAG, "Using TelephonyManager for slot $slotId") Log.d(TAG, "Using TelephonyManager for slot $slotId")
// TODO: On Tiramisu, we should also connect all available "ports" for MEP support // TODO: On Tiramisu, we should also connect all available "ports" for MEP support
TelephonyManagerApduChannel.tryConnectUiccSlot(tm, slotId)?.let { (_apduChannel, _stateManager) -> euiccChannel = TelephonyManagerChannel.tryConnect(tm, channelInfo)
apduChannel = _apduChannel
stateManager = _stateManager
}
} }
if (apduChannel == null || stateManager == null) { if (euiccChannel == null) {
OmapiApduChannel.tryConnectUiccSlot(seService!!, slotId) euiccChannel = OmapiChannel.tryConnect(seService!!, channelInfo)
?.let { (_apduChannel, _stateManager) ->
apduChannel = _apduChannel
stateManager = _stateManager
} ?: return null
} }
val channel = EuiccChannel( if (euiccChannel != null) {
slotId, cardId, channels.add(euiccChannel)
"SIM $slotId", }
LocalProfileAssistantImpl(apduChannel),
stateManager!!) return euiccChannel
channels.add(channel)
return channel
} }
fun findEuiccChannelBySlotBlocking(slotId: Int): EuiccChannel? = runBlocking { fun findEuiccChannelBySlotBlocking(slotId: Int): EuiccChannel? = runBlocking {
@ -113,7 +106,7 @@ class EuiccChannelManager(private val context: Context) {
fun invalidate() { fun invalidate() {
for (channel in channels) { for (channel in channels) {
channel.stateManager.destroy() channel.close()
} }
channels.clear() channels.clear()

View file

@ -1,41 +1,12 @@
package im.angry.openeuicc.core package im.angry.openeuicc.core
import android.se.omapi.Channel import android.se.omapi.Channel
import android.se.omapi.SEService
import android.util.Log
import com.truphone.lpa.ApduChannel import com.truphone.lpa.ApduChannel
import com.truphone.lpa.ApduTransmittedListener import com.truphone.lpa.ApduTransmittedListener
import im.angry.openeuicc.util.byteArrayToHex import im.angry.openeuicc.util.byteArrayToHex
import im.angry.openeuicc.util.hexStringToByteArray import im.angry.openeuicc.util.hexStringToByteArray
import java.lang.Exception
class OmapiApduChannel(private val channel: Channel) : ApduChannel { class OmapiApduChannel(private val channel: Channel) : ApduChannel {
companion object {
private const val TAG = "OmapiApduChannel"
private val APPLET_ID = byteArrayOf(-96, 0, 0, 5, 89, 16, 16, -1, -1, -1, -1, -119, 0, 0, 1, 0)
fun tryConnectUiccSlot(service: SEService, slotId: Int): Pair<ApduChannel, EuiccChannelStateManager>? {
try {
val reader = service.getUiccReader(slotId + 1) // slotId from telephony starts from 0
val session = reader.openSession()
val channel = session.openLogicalChannel(APPLET_ID) ?: return null
val stateManager = object : EuiccChannelStateManager {
override val valid: Boolean
get() = channel.isOpen
override fun destroy() {
channel.close()
}
}
return Pair(OmapiApduChannel(channel), stateManager)
} catch (e: Exception) {
Log.e(TAG, "Unable to open eUICC channel for slot ${slotId}, skipping")
Log.e(TAG, Log.getStackTraceString(e))
return null
}
}
}
override fun transmitAPDU(apdu: String): String = override fun transmitAPDU(apdu: String): String =
byteArrayToHex(channel.transmit(hexStringToByteArray(apdu))) byteArrayToHex(channel.transmit(hexStringToByteArray(apdu)))

View file

@ -0,0 +1,39 @@
package im.angry.openeuicc.core
import android.se.omapi.Channel
import android.se.omapi.SEService
import android.util.Log
import com.truphone.lpa.LocalProfileAssistant
import com.truphone.lpa.impl.LocalProfileAssistantImpl
import java.lang.Exception
class OmapiChannel private constructor(
info: EuiccChannelInfo,
private val channel: Channel,
) : EuiccChannel(info) {
companion object {
private const val TAG = "OmapiChannel"
private val APPLET_ID = byteArrayOf(-96, 0, 0, 5, 89, 16, 16, -1, -1, -1, -1, -119, 0, 0, 1, 0)
fun tryConnect(service: SEService, info: EuiccChannelInfo): OmapiChannel? {
try {
val reader = service.getUiccReader(info.slotId + 1) // slotId from telephony starts from 0
val session = reader.openSession()
val channel = session.openLogicalChannel(APPLET_ID) ?: return null
return OmapiChannel(info, channel)
} catch (e: Exception) {
Log.e(TAG, "Unable to open eUICC channel for slot ${info.slotId}, skipping")
Log.e(TAG, Log.getStackTraceString(e))
return null
}
}
}
override val lpa: LocalProfileAssistant by lazy {
LocalProfileAssistantImpl(OmapiApduChannel(channel))
}
override val valid: Boolean
get() = channel.isOpen // TODO: This has to be implemented properly
override fun close() = channel.close()
}

View file

@ -1,53 +1,15 @@
package im.angry.openeuicc.core package im.angry.openeuicc.core
import android.telephony.IccOpenLogicalChannelResponse.INVALID_CHANNEL
import android.telephony.IccOpenLogicalChannelResponse.STATUS_NO_ERROR
import android.telephony.TelephonyManager import android.telephony.TelephonyManager
import android.util.Log
import com.truphone.lpa.ApduChannel import com.truphone.lpa.ApduChannel
import com.truphone.lpa.ApduTransmittedListener import com.truphone.lpa.ApduTransmittedListener
import im.angry.openeuicc.util.* import im.angry.openeuicc.util.*
import java.lang.Exception
class TelephonyManagerApduChannel( class TelephonyManagerApduChannel(
private val tm: TelephonyManager, private val tm: TelephonyManager,
private val slotId: Int, private val slotId: Int,
private val channelId: Int) : ApduChannel { private val channelId: Int) : ApduChannel {
companion object {
private const val TAG = "TelephonyManagerApduChannel"
private const val EUICC_APP_ID = "A0000005591010FFFFFFFF8900000100"
// TODO: On Tiramisu, we need to specify the portId also if we want MEP support
fun tryConnectUiccSlot(tm: TelephonyManager, slotId: Int): Pair<ApduChannel, EuiccChannelStateManager>? {
try {
val channel = tm.iccOpenLogicalChannelBySlot(slotId, EUICC_APP_ID, 0)
if (channel.status != STATUS_NO_ERROR || channel.channel == INVALID_CHANNEL) {
Log.e(TAG, "Unable to open eUICC channel for slot ${slotId} via TelephonyManager: ${channel.status}")
return null
}
Log.d(TAG, "channel: ${channel.channel}")
val stateManager = object : EuiccChannelStateManager {
override val valid: Boolean
get() = true // TODO: Fix this properly
override fun destroy() {
tm.iccCloseLogicalChannelBySlot(slotId, channel.channel)
}
}
return Pair(TelephonyManagerApduChannel(tm, slotId, channel.channel), stateManager)
} catch (e: Exception) {
Log.e(TAG, "Unable to open eUICC channel for slot ${slotId} via TelephonyManager")
Log.e(TAG, Log.getStackTraceString(e))
return null
}
}
}
override fun transmitAPDU(apdu: String): String? { override fun transmitAPDU(apdu: String): String? {
val cla = Integer.parseInt(apdu.substring(0, 2), 16) val cla = Integer.parseInt(apdu.substring(0, 2), 16)
val instruction = Integer.parseInt(apdu.substring(2, 4), 16) val instruction = Integer.parseInt(apdu.substring(2, 4), 16)

View file

@ -0,0 +1,49 @@
package im.angry.openeuicc.core
import android.telephony.IccOpenLogicalChannelResponse
import android.telephony.TelephonyManager
import android.util.Log
import com.truphone.lpa.LocalProfileAssistant
import com.truphone.lpa.impl.LocalProfileAssistantImpl
import im.angry.openeuicc.util.*
import java.lang.Exception
class TelephonyManagerChannel private constructor(
info: EuiccChannelInfo,
private val tm: TelephonyManager,
private val channelId: Int
) : EuiccChannel(info) {
companion object {
private const val TAG = "TelephonyManagerApduChannel"
private const val EUICC_APP_ID = "A0000005591010FFFFFFFF8900000100"
// TODO: On Tiramisu, we need to specify the portId also if we want MEP support
fun tryConnect(tm: TelephonyManager, info: EuiccChannelInfo): TelephonyManagerChannel? {
try {
val channel = tm.iccOpenLogicalChannelBySlot(info.slotId, EUICC_APP_ID, 0)
if (channel.status != IccOpenLogicalChannelResponse.STATUS_NO_ERROR || channel.channel == IccOpenLogicalChannelResponse.INVALID_CHANNEL) {
Log.e(TAG, "Unable to open eUICC channel for slot ${info.slotId} via TelephonyManager: ${channel.status}")
return null
}
Log.d(TAG, "channel: ${channel.channel}")
return TelephonyManagerChannel(info, tm, channel.channel)
} catch (e: Exception) {
Log.e(TAG, "Unable to open eUICC channel for slot ${info.slotId} via TelephonyManager")
Log.e(TAG, Log.getStackTraceString(e))
return null
}
}
}
override val lpa: LocalProfileAssistant by lazy {
LocalProfileAssistantImpl(TelephonyManagerApduChannel(tm, slotId, channelId))
}
override val valid: Boolean
get() = true // TODO: Fix this
override fun close() {
tm.iccCloseLogicalChannelBySlot(slotId, channelId)
}
}