Compare commits

...
Sign in to create a new pull request.

2 commits

Author SHA1 Message Date
5dd9e40c25 Add seId support to most of EuiccChannelManager components 2025-06-02 22:24:11 -04:00
6bb05d910b [1/n] Add seId parameter to withEuiccChannel()
Defaults to 0 so that it doesn't break everything else.
2025-05-18 11:46:56 -04:00
9 changed files with 67 additions and 44 deletions

View file

@ -20,7 +20,8 @@ open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccCha
override suspend fun tryOpenEuiccChannel(
port: UiccPortInfoCompat,
isdrAid: ByteArray
isdrAid: ByteArray,
seId: Int,
): EuiccChannel? {
if (port.portIndex != 0) {
Log.w(
@ -46,6 +47,7 @@ open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccCha
context.preferenceRepository.verboseLoggingFlow
),
isdrAid,
seId,
context.preferenceRepository.verboseLoggingFlow,
context.preferenceRepository.ignoreTLSCertificateFlow,
).also {
@ -65,7 +67,8 @@ open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccCha
override fun tryOpenUsbEuiccChannel(
ccidCtx: UsbCcidContext,
isdrAid: ByteArray
isdrAid: ByteArray,
seId: Int
): EuiccChannel? {
try {
return EuiccChannelImpl(
@ -76,6 +79,7 @@ open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccCha
ccidCtx
),
isdrAid,
seId,
context.preferenceRepository.verboseLoggingFlow,
context.preferenceRepository.ignoreTLSCertificateFlow,
)

View file

@ -32,7 +32,7 @@ open class DefaultEuiccChannelManager(
private val channelCache = mutableListOf<EuiccChannel>()
private var usbChannel: EuiccChannel? = null
private var usbChannels = mutableListOf<EuiccChannel>()
private val lock = Mutex()
@ -51,15 +51,17 @@ open class DefaultEuiccChannelManager(
protected open val uiccCards: Collection<UiccCardInfoCompat>
get() = (0..<tm.activeModemCountCompat).map { FakeUiccCardInfoCompat(it) }
private suspend inline fun tryOpenChannelFirstValidAid(openFn: (ByteArray) -> EuiccChannel?): EuiccChannel? {
private suspend inline fun tryOpenChannelWithKnownAids(openFn: (ByteArray, Int) -> EuiccChannel?): List<EuiccChannel> {
val isdrAidList =
parseIsdrAidList(appContainer.preferenceRepository.isdrAidListFlow.first())
var seId = 0
return isdrAidList.firstNotNullOfOrNull {
Log.i(TAG, "Opening channel, trying ISDR AID ${it.encodeHex()}")
return isdrAidList.mapNotNull {
Log.i(TAG, "Opening channel, trying ISDR AID ${it.encodeHex()}, this will be seId ${seId}")
openFn(it)?.let { channel ->
openFn(it, seId)?.let { channel ->
if (channel.valid) {
seId += 1
channel
} else {
channel.close()
@ -69,19 +71,15 @@ open class DefaultEuiccChannelManager(
}
}
private suspend fun tryOpenEuiccChannel(port: UiccPortInfoCompat): EuiccChannel? {
private suspend fun tryOpenEuiccChannel(port: UiccPortInfoCompat, seId: Int = 0): EuiccChannel? {
lock.withLock {
if (port.card.physicalSlotIndex == EuiccChannelManager.USB_CHANNEL_ID) {
return if (usbChannel != null && usbChannel!!.valid) {
usbChannel
} else {
usbChannel = null
null
}
// We only compare seId because we assume we can only open 1 card from USB
return usbChannels.find { it.seId == seId }
}
val existing =
channelCache.find { it.slotId == port.card.physicalSlotIndex && it.portId == port.portIndex }
channelCache.find { it.slotId == port.card.physicalSlotIndex && it.portId == port.portIndex && it.seId == seId }
if (existing != null) {
if (existing.valid && port.logicalSlotIndex == existing.logicalSlotId) {
return existing
@ -96,12 +94,12 @@ open class DefaultEuiccChannelManager(
return null
}
val channel =
tryOpenChannelFirstValidAid { euiccChannelFactory.tryOpenEuiccChannel(port, it) }
val channels =
tryOpenChannelWithKnownAids { isdrAid, seId -> euiccChannelFactory.tryOpenEuiccChannel(port, isdrAid, seId) }
if (channel != null) {
channelCache.add(channel)
return channel
if (channels.isNotEmpty()) {
channelCache.addAll(channels)
return channels.find { it.seId == seId }
} else {
Log.i(
TAG,
@ -112,10 +110,10 @@ open class DefaultEuiccChannelManager(
}
}
protected suspend fun findEuiccChannelByLogicalSlot(logicalSlotId: Int): EuiccChannel? =
protected suspend fun findEuiccChannelByLogicalSlot(logicalSlotId: Int, seId: Int = 0): EuiccChannel? =
withContext(Dispatchers.IO) {
if (logicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) {
return@withContext usbChannel
return@withContext usbChannels.find { it.seId == seId }
}
for (card in uiccCards) {
@ -131,7 +129,7 @@ open class DefaultEuiccChannelManager(
private suspend fun findAllEuiccChannelsByPhysicalSlot(physicalSlotId: Int): List<EuiccChannel>? {
if (physicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) {
return usbChannel?.let { listOf(it) }
return usbChannels.ifEmpty { null }
}
for (card in uiccCards) {
@ -142,14 +140,14 @@ open class DefaultEuiccChannelManager(
return null
}
private suspend fun findEuiccChannelByPort(physicalSlotId: Int, portId: Int): EuiccChannel? =
private suspend fun findEuiccChannelByPort(physicalSlotId: Int, portId: Int, seId: Int = 0): EuiccChannel? =
withContext(Dispatchers.IO) {
if (physicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) {
return@withContext usbChannel
return@withContext usbChannels.find { it.seId == seId }
}
uiccCards.find { it.physicalSlotIndex == physicalSlotId }?.let { card ->
card.ports.find { it.portIndex == portId }?.let { tryOpenEuiccChannel(it) }
card.ports.find { it.portIndex == portId }?.let { tryOpenEuiccChannel(it, seId) }
}
}
@ -168,15 +166,16 @@ open class DefaultEuiccChannelManager(
return@withContext listOf(0)
}
findAllEuiccChannelsByPhysicalSlot(physicalSlotId)?.map { it.portId } ?: listOf()
findAllEuiccChannelsByPhysicalSlot(physicalSlotId)?.map { it.portId }?.toSet()?.toList() ?: listOf()
}
override suspend fun <R> withEuiccChannel(
physicalSlotId: Int,
portId: Int,
seId: Int,
fn: suspend (EuiccChannel) -> R
): R {
val channel = findEuiccChannelByPort(physicalSlotId, portId)
val channel = findEuiccChannelByPort(physicalSlotId, portId, seId)
?: throw EuiccChannelManager.EuiccChannelNotFoundException()
val wrapper = EuiccChannelWrapper(channel)
try {
@ -190,9 +189,10 @@ open class DefaultEuiccChannelManager(
override suspend fun <R> withEuiccChannel(
logicalSlotId: Int,
seId: Int,
fn: suspend (EuiccChannel) -> R
): R {
val channel = findEuiccChannelByLogicalSlot(logicalSlotId)
val channel = findEuiccChannelByLogicalSlot(logicalSlotId, seId)
?: throw EuiccChannelManager.EuiccChannelNotFoundException()
val wrapper = EuiccChannelWrapper(channel)
try {
@ -206,8 +206,8 @@ open class DefaultEuiccChannelManager(
override suspend fun waitForReconnect(physicalSlotId: Int, portId: Int, timeoutMillis: Long) {
if (physicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) {
usbChannel?.close()
usbChannel = null
usbChannels.forEach { it.close() }
usbChannels.clear()
} else {
// If there is already a valid channel, we close it proactively
// Sometimes the current channel can linger on for a bit even after it should have become invalid
@ -223,7 +223,7 @@ open class DefaultEuiccChannelManager(
// tryOpenUsbEuiccChannel() will always try to reopen the channel, even if
// a USB channel already exists
tryOpenUsbEuiccChannel()
usbChannel!!
usbChannels.getOrNull(0)!!
} else {
// tryOpenEuiccChannel() will automatically dispose of invalid channels
// and recreate when needed
@ -280,12 +280,13 @@ open class DefaultEuiccChannelManager(
val ccidCtx = UsbCcidContext.createFromUsbDevice(context, device, iface) ?: return@forEach
try {
val channel = tryOpenChannelFirstValidAid {
euiccChannelFactory.tryOpenUsbEuiccChannel(ccidCtx, it)
val channels = tryOpenChannelWithKnownAids { isdrAid, seId ->
euiccChannelFactory.tryOpenUsbEuiccChannel(ccidCtx, isdrAid, seId)
}
if (channel != null && channel.lpa.valid) {
if (channels.isNotEmpty() && channels[0].valid) {
ccidCtx.allowDisconnect = true
usbChannel = channel
usbChannels.clear()
usbChannels.addAll(channels)
return@withContext Pair(device, true)
}
} catch (e: Exception) {
@ -309,8 +310,8 @@ open class DefaultEuiccChannelManager(
channel.close()
}
usbChannel?.close()
usbChannel = null
usbChannels.forEach { it.close() }
usbChannels.clear()
channelCache.clear()
euiccChannelFactory.cleanup()
}

View file

@ -13,6 +13,16 @@ interface EuiccChannel {
val logicalSlotId: Int
val portId: Int
/**
* Some chips support multiple SEs on one chip. The seId here is intended
* to distinguish channels opened from these different SEs.
*
* Note that this ID is arbitrary and heavily depends on the order in which
* we attempt to open the ISD-R AIDs. As such, it shall not be treated with
* any significance other than as a transient ID.
*/
val seId: Int
val lpa: LocalProfileAssistant
val valid: Boolean

View file

@ -6,11 +6,12 @@ import im.angry.openeuicc.util.*
// This class is here instead of inside DI because it contains a bit more logic than just
// "dumb" dependency injection.
interface EuiccChannelFactory {
suspend fun tryOpenEuiccChannel(port: UiccPortInfoCompat, isdrAid: ByteArray): EuiccChannel?
suspend fun tryOpenEuiccChannel(port: UiccPortInfoCompat, isdrAid: ByteArray, seInt: Int): EuiccChannel?
fun tryOpenUsbEuiccChannel(
ccidCtx: UsbCcidContext,
isdrAid: ByteArray
isdrAid: ByteArray,
seInt: Int
): EuiccChannel?
/**

View file

@ -14,6 +14,7 @@ class EuiccChannelImpl(
override val intrinsicChannelName: String?,
override val apduInterface: ApduInterface,
override val isdrAid: ByteArray,
override val seId: Int,
verboseLoggingFlow: Flow<Boolean>,
ignoreTLSCertificateFlow: Flow<Boolean>
) : EuiccChannel {

View file

@ -81,6 +81,7 @@ interface EuiccChannelManager {
suspend fun <R> withEuiccChannel(
physicalSlotId: Int,
portId: Int,
seId: Int = 0,
fn: suspend (EuiccChannel) -> R
): R
@ -89,6 +90,7 @@ interface EuiccChannelManager {
*/
suspend fun <R> withEuiccChannel(
logicalSlotId: Int,
seId: Int = 0,
fn: suspend (EuiccChannel) -> R
): R

View file

@ -26,6 +26,8 @@ class EuiccChannelWrapper(orig: EuiccChannel) : EuiccChannel {
get() = channel.logicalSlotId
override val portId: Int
get() = channel.portId
override val seId: Int
get() = channel.seId
private val lpaDelegate = lazy {
LocalProfileAssistantWrapper(channel.lpa)
}

View file

@ -92,7 +92,7 @@ class EuiccInfoActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
lifecycleScope.launch {
(infoList.adapter!! as EuiccInfoAdapter).euiccInfoItems =
euiccChannelManager.withEuiccChannel(logicalSlotId, ::buildEuiccInfoItems)
euiccChannelManager.withEuiccChannel(logicalSlotId, fn = ::buildEuiccInfoItems)
swipeRefresh.isRefreshing = false
}

View file

@ -16,13 +16,14 @@ class PrivilegedEuiccChannelFactory(context: Context) : DefaultEuiccChannelFacto
@Suppress("NAME_SHADOWING")
override suspend fun tryOpenEuiccChannel(
port: UiccPortInfoCompat,
isdrAid: ByteArray
isdrAid: ByteArray,
seId: Int,
): EuiccChannel? {
val port = port as RealUiccPortInfoCompat
if (port.card.isRemovable) {
// Attempt unprivileged (OMAPI) before TelephonyManager
// but still try TelephonyManager in case OMAPI is broken
super.tryOpenEuiccChannel(port, isdrAid)?.let { return it }
super.tryOpenEuiccChannel(port, isdrAid, seId)?.let { return it }
}
if (port.card.isEuicc || preferenceRepository.removableTelephonyManagerFlow.first()) {
@ -41,6 +42,7 @@ class PrivilegedEuiccChannelFactory(context: Context) : DefaultEuiccChannelFacto
context.preferenceRepository.verboseLoggingFlow
),
isdrAid,
seId,
context.preferenceRepository.verboseLoggingFlow,
context.preferenceRepository.ignoreTLSCertificateFlow,
)
@ -53,6 +55,6 @@ class PrivilegedEuiccChannelFactory(context: Context) : DefaultEuiccChannelFacto
}
}
return super.tryOpenEuiccChannel(port, isdrAid)
return super.tryOpenEuiccChannel(port, isdrAid, seId)
}
}