refactor: Extract an interface from EuiccChannelManager
All checks were successful
/ build-debug (push) Successful in 4m45s

Eventually, we would like EuiccChannelManager to become a Service
instead of just any random class.
This commit is contained in:
Peter Cai 2024-03-03 20:29:18 -05:00
parent e48a919335
commit 770083523d
7 changed files with 105 additions and 56 deletions

View file

@ -5,6 +5,7 @@ import android.telephony.SubscriptionManager
import android.telephony.TelephonyManager import android.telephony.TelephonyManager
import com.google.android.material.color.DynamicColors import com.google.android.material.color.DynamicColors
import im.angry.openeuicc.core.EuiccChannelManager import im.angry.openeuicc.core.EuiccChannelManager
import im.angry.openeuicc.core.IEuiccChannelManager
import im.angry.openeuicc.util.PreferenceRepository import im.angry.openeuicc.util.PreferenceRepository
open class OpenEuiccApplication : Application() { open class OpenEuiccApplication : Application() {
@ -19,7 +20,7 @@ open class OpenEuiccApplication : Application() {
getSystemService(TelephonyManager::class.java)!! getSystemService(TelephonyManager::class.java)!!
} }
open val euiccChannelManager: EuiccChannelManager by lazy { open val euiccChannelManager: IEuiccChannelManager by lazy {
EuiccChannelManager(this) EuiccChannelManager(this)
} }

View file

@ -13,7 +13,7 @@ import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.lang.IllegalArgumentException import java.lang.IllegalArgumentException
open class EuiccChannelManager(protected val context: Context) { open class EuiccChannelManager(protected val context: Context) : IEuiccChannelManager {
companion object { companion object {
const val TAG = "EuiccChannelManager" const val TAG = "EuiccChannelManager"
} }
@ -32,9 +32,9 @@ open class EuiccChannelManager(protected val context: Context) {
get() = (0..<tm.activeModemCountCompat).map { FakeUiccCardInfoCompat(it) } get() = (0..<tm.activeModemCountCompat).map { FakeUiccCardInfoCompat(it) }
private suspend fun ensureSEService() { private suspend fun ensureSEService() {
if (seService == null) { if (seService == null) {
seService = connectSEService(context) seService = connectSEService(context)
} }
} }
protected open fun tryOpenEuiccChannelPrivileged(port: UiccPortInfoCompat): EuiccChannel? { protected open fun tryOpenEuiccChannelPrivileged(port: UiccPortInfoCompat): EuiccChannel? {
@ -52,7 +52,10 @@ open class EuiccChannelManager(protected val context: Context) {
return OmapiChannel(seService!!, port) return OmapiChannel(seService!!, port)
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
// Failed // Failed
Log.w(TAG, "OMAPI APDU interface unavailable for physical slot ${port.card.physicalSlotIndex}.") Log.w(
TAG,
"OMAPI APDU interface unavailable for physical slot ${port.card.physicalSlotIndex}."
)
} }
return null return null
@ -61,7 +64,8 @@ open class EuiccChannelManager(protected val context: Context) {
private suspend fun tryOpenEuiccChannel(port: UiccPortInfoCompat): EuiccChannel? { private suspend fun tryOpenEuiccChannel(port: UiccPortInfoCompat): EuiccChannel? {
lock.withLock { lock.withLock {
ensureSEService() ensureSEService()
val existing = channels.find { it.slotId == port.card.physicalSlotIndex && it.portId == port.portIndex } val existing =
channels.find { it.slotId == port.card.physicalSlotIndex && it.portId == port.portIndex }
if (existing != null) { if (existing != null) {
if (existing.valid && port.logicalSlotIndex == existing.logicalSlotId) { if (existing.valid && port.logicalSlotIndex == existing.logicalSlotId) {
return existing return existing
@ -90,7 +94,7 @@ open class EuiccChannelManager(protected val context: Context) {
} }
} }
fun findEuiccChannelBySlotBlocking(logicalSlotId: Int): EuiccChannel? = override fun findEuiccChannelBySlotBlocking(logicalSlotId: Int): EuiccChannel? =
runBlocking { runBlocking {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
for (card in uiccCards) { for (card in uiccCards) {
@ -105,54 +109,60 @@ open class EuiccChannelManager(protected val context: Context) {
} }
} }
fun findEuiccChannelByPhysicalSlotBlocking(physicalSlotId: Int): EuiccChannel? = runBlocking { override fun findEuiccChannelByPhysicalSlotBlocking(physicalSlotId: Int): EuiccChannel? =
withContext(Dispatchers.IO) { runBlocking {
withContext(Dispatchers.IO) {
for (card in uiccCards) {
if (card.physicalSlotIndex != physicalSlotId) continue
for (port in card.ports) {
tryOpenEuiccChannel(port)?.let { return@withContext it }
}
}
null
}
}
override fun findAllEuiccChannelsByPhysicalSlotBlocking(physicalSlotId: Int): List<EuiccChannel>? =
runBlocking {
for (card in uiccCards) { for (card in uiccCards) {
if (card.physicalSlotIndex != physicalSlotId) continue if (card.physicalSlotIndex != physicalSlotId) continue
for (port in card.ports) { return@runBlocking card.ports.mapNotNull { tryOpenEuiccChannel(it) }
tryOpenEuiccChannel(port)?.let { return@withContext it } .ifEmpty { null }
}
return@runBlocking null
}
override fun findEuiccChannelByPortBlocking(physicalSlotId: Int, portId: Int): EuiccChannel? =
runBlocking {
withContext(Dispatchers.IO) {
uiccCards.find { it.physicalSlotIndex == physicalSlotId }?.let { card ->
card.ports.find { it.portIndex == portId }?.let { tryOpenEuiccChannel(it) }
} }
} }
null
} }
}
fun findAllEuiccChannelsByPhysicalSlotBlocking(physicalSlotId: Int): List<EuiccChannel>? = runBlocking { override suspend fun enumerateEuiccChannels() {
for (card in uiccCards) {
if (card.physicalSlotIndex != physicalSlotId) continue
return@runBlocking card.ports.mapNotNull { tryOpenEuiccChannel(it) }
.ifEmpty { null }
}
return@runBlocking null
}
fun findEuiccChannelByPortBlocking(physicalSlotId: Int, portId: Int): EuiccChannel? = runBlocking {
withContext(Dispatchers.IO) {
uiccCards.find { it.physicalSlotIndex == physicalSlotId }?.let { card ->
card.ports.find { it.portIndex == portId }?.let { tryOpenEuiccChannel(it) }
}
}
}
suspend fun enumerateEuiccChannels() {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
ensureSEService() ensureSEService()
for (uiccInfo in uiccCards) { for (uiccInfo in uiccCards) {
for (port in uiccInfo.ports) { for (port in uiccInfo.ports) {
if (tryOpenEuiccChannel(port) != null) { if (tryOpenEuiccChannel(port) != null) {
Log.d(TAG, "Found eUICC on slot ${uiccInfo.physicalSlotIndex} port ${port.portIndex}") Log.d(
TAG,
"Found eUICC on slot ${uiccInfo.physicalSlotIndex} port ${port.portIndex}"
)
} }
} }
} }
} }
} }
val knownChannels: List<EuiccChannel> override val knownChannels: List<EuiccChannel>
get() = channels.toList() get() = channels.toList()
fun invalidate() { override fun invalidate() {
for (channel in channels) { for (channel in channels) {
channel.close() channel.close()
} }
@ -161,8 +171,4 @@ open class EuiccChannelManager(protected val context: Context) {
seService?.shutdown() seService?.shutdown()
seService = null seService = null
} }
open fun notifyEuiccProfilesChanged(logicalSlotId: Int) {
// No-op for unprivileged
}
} }

View file

@ -0,0 +1,47 @@
package im.angry.openeuicc.core
interface IEuiccChannelManager {
val knownChannels: List<EuiccChannel>
/**
* Scan all possible sources for EuiccChannels and have them cached for future use
*/
suspend fun enumerateEuiccChannels()
/**
* Returns the EuiccChannel corresponding to a **logical** slot
*/
fun findEuiccChannelBySlotBlocking(logicalSlotId: Int): EuiccChannel?
/**
* Returns the first EuiccChannel corresponding to a **physical** slot
* If the physical slot supports MEP and has multiple ports, it is undefined
* which of the two channels will be returned.
*/
fun findEuiccChannelByPhysicalSlotBlocking(physicalSlotId: Int): EuiccChannel?
/**
* Returns all EuiccChannels corresponding to a **physical** slot
* Multiple channels are possible in the case of MEP
*/
fun findAllEuiccChannelsByPhysicalSlotBlocking(physicalSlotId: Int): List<EuiccChannel>?
/**
* Returns the EuiccChannel corresponding to a **physical** slot and a port ID
*/
fun findEuiccChannelByPortBlocking(physicalSlotId: Int, portId: Int): EuiccChannel?
/**
* Invalidate all EuiccChannels previously known by this Manager
*/
fun invalidate()
/**
* If possible, trigger the system to update the cached list of profiles
* This is only expected to be implemented when the application is privileged
* TODO: Remove this from the common interface
*/
fun notifyEuiccProfilesChanged(logicalSlotId: Int) {
// no-op by default
}
}

View file

@ -14,7 +14,6 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import im.angry.openeuicc.common.R import im.angry.openeuicc.common.R
import im.angry.openeuicc.core.EuiccChannel import im.angry.openeuicc.core.EuiccChannel
import im.angry.openeuicc.core.EuiccChannelManager
import im.angry.openeuicc.util.* import im.angry.openeuicc.util.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -25,8 +24,6 @@ open class MainActivity : AppCompatActivity(), OpenEuiccContextMarker {
const val TAG = "MainActivity" const val TAG = "MainActivity"
} }
protected lateinit var manager: EuiccChannelManager
private lateinit var spinnerAdapter: ArrayAdapter<String> private lateinit var spinnerAdapter: ArrayAdapter<String>
private lateinit var spinner: Spinner private lateinit var spinner: Spinner
@ -45,8 +42,6 @@ open class MainActivity : AppCompatActivity(), OpenEuiccContextMarker {
tm = telephonyManager tm = telephonyManager
manager = euiccChannelManager
spinnerAdapter = ArrayAdapter<String>(this, R.layout.spinner_item) spinnerAdapter = ArrayAdapter<String>(this, R.layout.spinner_item)
lifecycleScope.launch { lifecycleScope.launch {
@ -99,19 +94,19 @@ open class MainActivity : AppCompatActivity(), OpenEuiccContextMarker {
private suspend fun init() { private suspend fun init() {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
manager.enumerateEuiccChannels() euiccChannelManager.enumerateEuiccChannels()
manager.knownChannels.forEach { euiccChannelManager.knownChannels.forEach {
Log.d(TAG, "slot ${it.slotId} port ${it.portId}") Log.d(TAG, "slot ${it.slotId} port ${it.portId}")
Log.d(TAG, it.lpa.eID) Log.d(TAG, it.lpa.eID)
// Request the system to refresh the list of profiles every time we start // Request the system to refresh the list of profiles every time we start
// Note that this is currently supposed to be no-op when unprivileged, // Note that this is currently supposed to be no-op when unprivileged,
// but it could change in the future // but it could change in the future
manager.notifyEuiccProfilesChanged(it.logicalSlotId) euiccChannelManager.notifyEuiccProfilesChanged(it.logicalSlotId)
} }
} }
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
manager.knownChannels.sortedBy { it.logicalSlotId }.forEach { channel -> euiccChannelManager.knownChannels.sortedBy { it.logicalSlotId }.forEach { channel ->
spinnerAdapter.add(getString(R.string.channel_name_format, channel.logicalSlotId)) spinnerAdapter.add(getString(R.string.channel_name_format, channel.logicalSlotId))
fragments.add(createEuiccManagementFragment(channel)) fragments.add(createEuiccManagementFragment(channel))
} }

View file

@ -7,7 +7,7 @@ import android.telephony.TelephonyManager
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import im.angry.openeuicc.OpenEuiccApplication import im.angry.openeuicc.OpenEuiccApplication
import im.angry.openeuicc.core.EuiccChannel import im.angry.openeuicc.core.EuiccChannel
import im.angry.openeuicc.core.EuiccChannelManager import im.angry.openeuicc.core.IEuiccChannelManager
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
@ -48,7 +48,7 @@ interface OpenEuiccContextMarker {
val openEuiccApplication: OpenEuiccApplication val openEuiccApplication: OpenEuiccApplication
get() = openEuiccMarkerContext.applicationContext as OpenEuiccApplication get() = openEuiccMarkerContext.applicationContext as OpenEuiccApplication
val euiccChannelManager: EuiccChannelManager val euiccChannelManager: IEuiccChannelManager
get() = openEuiccApplication.euiccChannelManager get() = openEuiccApplication.euiccChannelManager
val telephonyManager: TelephonyManager val telephonyManager: TelephonyManager

View file

@ -1,10 +1,10 @@
package im.angry.openeuicc package im.angry.openeuicc
import im.angry.openeuicc.core.EuiccChannelManager import im.angry.openeuicc.core.IEuiccChannelManager
import im.angry.openeuicc.core.PrivilegedEuiccChannelManager import im.angry.openeuicc.core.PrivilegedEuiccChannelManager
class PrivilegedOpenEuiccApplication: OpenEuiccApplication() { class PrivilegedOpenEuiccApplication: OpenEuiccApplication() {
override val euiccChannelManager: EuiccChannelManager by lazy { override val euiccChannelManager: IEuiccChannelManager by lazy {
PrivilegedEuiccChannelManager(this) PrivilegedEuiccChannelManager(this)
} }

View file

@ -4,7 +4,7 @@ import android.telephony.SubscriptionManager
import android.telephony.TelephonyManager import android.telephony.TelephonyManager
import android.telephony.UiccSlotMapping import android.telephony.UiccSlotMapping
import im.angry.openeuicc.core.EuiccChannel import im.angry.openeuicc.core.EuiccChannel
import im.angry.openeuicc.core.EuiccChannelManager import im.angry.openeuicc.core.IEuiccChannelManager
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import java.lang.Exception import java.lang.Exception
@ -14,7 +14,7 @@ val TelephonyManager.supportsDSDS: Boolean
val TelephonyManager.dsdsEnabled: Boolean val TelephonyManager.dsdsEnabled: Boolean
get() = activeModemCount >= 2 get() = activeModemCount >= 2
fun TelephonyManager.setDsdsEnabled(euiccManager: EuiccChannelManager, enabled: Boolean) { fun TelephonyManager.setDsdsEnabled(euiccManager: IEuiccChannelManager, enabled: Boolean) {
runBlocking { runBlocking {
euiccManager.enumerateEuiccChannels() euiccManager.enumerateEuiccChannels()
} }
@ -32,7 +32,7 @@ fun TelephonyManager.setDsdsEnabled(euiccManager: EuiccChannelManager, enabled:
// Disable eSIM profiles before switching the slot mapping // Disable eSIM profiles before switching the slot mapping
// This ensures that unmapped eSIM ports never have "ghost" profiles enabled // This ensures that unmapped eSIM ports never have "ghost" profiles enabled
fun TelephonyManager.updateSimSlotMapping( fun TelephonyManager.updateSimSlotMapping(
euiccManager: EuiccChannelManager, newMapping: Collection<UiccSlotMapping>, euiccManager: IEuiccChannelManager, newMapping: Collection<UiccSlotMapping>,
currentMapping: Collection<UiccSlotMapping> = simSlotMapping currentMapping: Collection<UiccSlotMapping> = simSlotMapping
) { ) {
val unmapped = currentMapping.filterNot { mapping -> val unmapped = currentMapping.filterNot { mapping ->