diff --git a/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelFactory.kt b/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelFactory.kt index 0de99b5..5755c02 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelFactory.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelFactory.kt @@ -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, ) diff --git a/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelManager.kt b/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelManager.kt index 6b336cd..5f18295 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelManager.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelManager.kt @@ -32,7 +32,7 @@ open class DefaultEuiccChannelManager( private val channelCache = mutableListOf() - private var usbChannel: EuiccChannel? = null + private var usbChannels = mutableListOf() private val lock = Mutex() @@ -51,15 +51,17 @@ open class DefaultEuiccChannelManager( protected open val uiccCards: Collection get() = (0.. EuiccChannel?): EuiccChannel? { + private suspend inline fun tryOpenChannelWithKnownAids(openFn: (ByteArray, Int) -> EuiccChannel?): List { 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? { 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 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 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() } diff --git a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannel.kt b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannel.kt index b20932f..9854bb2 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannel.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannel.kt @@ -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 diff --git a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelFactory.kt b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelFactory.kt index ba587a6..53ac7ef 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelFactory.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelFactory.kt @@ -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? /** diff --git a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelImpl.kt b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelImpl.kt index 2a33c20..bf98397 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelImpl.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelImpl.kt @@ -14,6 +14,7 @@ class EuiccChannelImpl( override val intrinsicChannelName: String?, override val apduInterface: ApduInterface, override val isdrAid: ByteArray, + override val seId: Int, verboseLoggingFlow: Flow, ignoreTLSCertificateFlow: Flow ) : EuiccChannel { diff --git a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelManager.kt b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelManager.kt index 17f3130..ffa3606 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelManager.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelManager.kt @@ -81,6 +81,7 @@ interface EuiccChannelManager { suspend fun withEuiccChannel( physicalSlotId: Int, portId: Int, + seId: Int = 0, fn: suspend (EuiccChannel) -> R ): R @@ -89,6 +90,7 @@ interface EuiccChannelManager { */ suspend fun withEuiccChannel( logicalSlotId: Int, + seId: Int = 0, fn: suspend (EuiccChannel) -> R ): R diff --git a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelWrapper.kt b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelWrapper.kt index 361a943..8496189 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelWrapper.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelWrapper.kt @@ -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) } diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/EuiccInfoActivity.kt b/app-common/src/main/java/im/angry/openeuicc/ui/EuiccInfoActivity.kt index 1d5f37f..f0bff39 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/EuiccInfoActivity.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/EuiccInfoActivity.kt @@ -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 } diff --git a/app/src/main/java/im/angry/openeuicc/core/PrivilegedEuiccChannelFactory.kt b/app/src/main/java/im/angry/openeuicc/core/PrivilegedEuiccChannelFactory.kt index 68eddef..cb2bbc3 100644 --- a/app/src/main/java/im/angry/openeuicc/core/PrivilegedEuiccChannelFactory.kt +++ b/app/src/main/java/im/angry/openeuicc/core/PrivilegedEuiccChannelFactory.kt @@ -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) } } \ No newline at end of file