refactor: Use DI techniques for EuiccChannel's as well

This commit is contained in:
Peter Cai 2024-03-04 19:30:04 -05:00
parent 7c6b4ebee5
commit 1a69c5294b
8 changed files with 133 additions and 80 deletions

View file

@ -0,0 +1,43 @@
package im.angry.openeuicc.core
import android.content.Context
import android.se.omapi.SEService
import android.util.Log
import im.angry.openeuicc.util.*
import java.lang.IllegalArgumentException
open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccChannelFactory {
private var seService: SEService? = null
private suspend fun ensureSEService() {
if (seService == null) {
seService = connectSEService(context)
}
}
override suspend fun tryOpenEuiccChannel(port: UiccPortInfoCompat): EuiccChannel? {
if (port.portIndex != 0) {
Log.w(DefaultEuiccChannelManager.TAG, "OMAPI channel attempted on non-zero portId, this may or may not work.")
}
ensureSEService()
Log.i(DefaultEuiccChannelManager.TAG, "Trying OMAPI for physical slot ${port.card.physicalSlotIndex}")
try {
return OmapiChannel(seService!!, port)
} catch (e: IllegalArgumentException) {
// Failed
Log.w(
DefaultEuiccChannelManager.TAG,
"OMAPI APDU interface unavailable for physical slot ${port.card.physicalSlotIndex}."
)
}
return null
}
override fun cleanup() {
seService?.shutdown()
seService = null
}
}

View file

@ -1,69 +1,41 @@
package im.angry.openeuicc.core
import android.content.Context
import android.se.omapi.SEService
import android.telephony.SubscriptionManager
import android.util.Log
import im.angry.openeuicc.OpenEuiccApplication
import im.angry.openeuicc.di.AppContainer
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.IllegalArgumentException
open class DefaultEuiccChannelManager(protected val context: Context) : EuiccChannelManager {
open class DefaultEuiccChannelManager(
protected val appContainer: AppContainer,
protected val context: Context
) : EuiccChannelManager {
companion object {
const val TAG = "EuiccChannelManager"
}
private val channels = mutableListOf<EuiccChannel>()
private var seService: SEService? = null
private val lock = Mutex()
protected val tm by lazy {
(context.applicationContext as OpenEuiccApplication).appContainer.telephonyManager
appContainer.telephonyManager
}
private val euiccChannelFactory by lazy {
appContainer.euiccChannelFactory
}
protected open val uiccCards: Collection<UiccCardInfoCompat>
get() = (0..<tm.activeModemCountCompat).map { FakeUiccCardInfoCompat(it) }
private suspend fun ensureSEService() {
if (seService == null) {
seService = connectSEService(context)
}
}
protected open fun tryOpenEuiccChannelPrivileged(port: UiccPortInfoCompat): EuiccChannel? {
// No-op when unprivileged
return null
}
protected fun tryOpenEuiccChannelUnprivileged(port: UiccPortInfoCompat): EuiccChannel? {
if (port.portIndex != 0) {
Log.w(TAG, "OMAPI channel attempted on non-zero portId, this may or may not work.")
}
Log.i(TAG, "Trying OMAPI for physical slot ${port.card.physicalSlotIndex}")
try {
return OmapiChannel(seService!!, port)
} catch (e: IllegalArgumentException) {
// Failed
Log.w(
TAG,
"OMAPI APDU interface unavailable for physical slot ${port.card.physicalSlotIndex}."
)
}
return null
}
private suspend fun tryOpenEuiccChannel(port: UiccPortInfoCompat): EuiccChannel? {
lock.withLock {
ensureSEService()
val existing =
channels.find { it.slotId == port.card.physicalSlotIndex && it.portId == port.portIndex }
if (existing != null) {
@ -80,17 +52,9 @@ open class DefaultEuiccChannelManager(protected val context: Context) : EuiccCha
return null
}
var euiccChannel: EuiccChannel? = tryOpenEuiccChannelPrivileged(port)
if (euiccChannel == null) {
euiccChannel = tryOpenEuiccChannelUnprivileged(port)
return euiccChannelFactory.tryOpenEuiccChannel(port)?.also {
channels.add(it)
}
if (euiccChannel != null) {
channels.add(euiccChannel)
}
return euiccChannel
}
}
@ -144,8 +108,6 @@ open class DefaultEuiccChannelManager(protected val context: Context) : EuiccCha
override suspend fun enumerateEuiccChannels() {
withContext(Dispatchers.IO) {
ensureSEService()
for (uiccInfo in uiccCards) {
for (port in uiccInfo.ports) {
if (tryOpenEuiccChannel(port) != null) {
@ -168,7 +130,6 @@ open class DefaultEuiccChannelManager(protected val context: Context) : EuiccCha
}
channels.clear()
seService?.shutdown()
seService = null
euiccChannelFactory.cleanup()
}
}

View file

@ -0,0 +1,16 @@
package im.angry.openeuicc.core
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): EuiccChannel?
/**
* Release all resources used by this EuiccChannelFactory
* Note that the same instance may be reused; any resources allocated must be automatically
* re-acquired when this happens
*/
fun cleanup()
}

View file

@ -2,6 +2,7 @@ package im.angry.openeuicc.di
import android.telephony.SubscriptionManager
import android.telephony.TelephonyManager
import im.angry.openeuicc.core.EuiccChannelFactory
import im.angry.openeuicc.core.EuiccChannelManager
import im.angry.openeuicc.util.*
@ -11,4 +12,5 @@ interface AppContainer {
val subscriptionManager: SubscriptionManager
val preferenceRepository: PreferenceRepository
val uiComponentFactory: UiComponentFactory
val euiccChannelFactory: EuiccChannelFactory
}

View file

@ -3,6 +3,7 @@ package im.angry.openeuicc.di
import android.content.Context
import android.telephony.SubscriptionManager
import android.telephony.TelephonyManager
import im.angry.openeuicc.core.DefaultEuiccChannelFactory
import im.angry.openeuicc.core.DefaultEuiccChannelManager
import im.angry.openeuicc.core.EuiccChannelManager
import im.angry.openeuicc.util.*
@ -13,7 +14,7 @@ open class DefaultAppContainer(context: Context) : AppContainer {
}
override val euiccChannelManager: EuiccChannelManager by lazy {
DefaultEuiccChannelManager(context)
DefaultEuiccChannelManager(this, context)
}
override val subscriptionManager by lazy {
@ -27,4 +28,8 @@ open class DefaultAppContainer(context: Context) : AppContainer {
override val uiComponentFactory by lazy {
DefaultUiComponentFactory()
}
override val euiccChannelFactory by lazy {
DefaultEuiccChannelFactory(context)
}
}

View file

@ -0,0 +1,43 @@
package im.angry.openeuicc.core
import android.content.Context
import android.util.Log
import im.angry.openeuicc.OpenEuiccApplication
import im.angry.openeuicc.util.*
import java.lang.IllegalArgumentException
class PrivilegedEuiccChannelFactory(context: Context) : DefaultEuiccChannelFactory(context) {
private val tm by lazy {
(context.applicationContext as OpenEuiccApplication).appContainer.telephonyManager
}
@Suppress("NAME_SHADOWING")
override suspend fun tryOpenEuiccChannel(port: UiccPortInfoCompat): 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)?.let { return it }
}
if (port.card.isEuicc) {
Log.i(
DefaultEuiccChannelManager.TAG,
"Trying TelephonyManager for slot ${port.card.physicalSlotIndex} port ${port.portIndex}"
)
try {
return TelephonyManagerChannel(
port, tm
)
} catch (e: IllegalArgumentException) {
// Failed
Log.w(
DefaultEuiccChannelManager.TAG,
"TelephonyManager APDU interface unavailable for slot ${port.card.physicalSlotIndex} port ${port.portIndex}, falling back"
)
}
}
return super.tryOpenEuiccChannel(port)
}
}

View file

@ -1,37 +1,15 @@
package im.angry.openeuicc.core
import android.content.Context
import android.util.Log
import im.angry.openeuicc.OpenEuiccApplication
import im.angry.openeuicc.di.AppContainer
import im.angry.openeuicc.util.*
import java.lang.Exception
import java.lang.IllegalArgumentException
class PrivilegedEuiccChannelManager(context: Context): DefaultEuiccChannelManager(context) {
class PrivilegedEuiccChannelManager(appContainer: AppContainer, context: Context) :
DefaultEuiccChannelManager(appContainer, context) {
override val uiccCards: Collection<UiccCardInfoCompat>
get() = tm.uiccCardsInfoCompat
@Suppress("NAME_SHADOWING")
override fun tryOpenEuiccChannelPrivileged(port: UiccPortInfoCompat): 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.tryOpenEuiccChannelUnprivileged(port)?.let { return it }
}
if (port.card.isEuicc) {
Log.i(TAG, "Trying TelephonyManager for slot ${port.card.physicalSlotIndex} port ${port.portIndex}")
try {
return TelephonyManagerChannel(port, tm)
} catch (e: IllegalArgumentException) {
// Failed
Log.w(TAG, "TelephonyManager APDU interface unavailable for slot ${port.card.physicalSlotIndex} port ${port.portIndex}, falling back")
}
}
return null
}
// Clean up channels left open in TelephonyManager
// due to a (potentially) forced restart
// This should be called every time the application is restarted
@ -48,7 +26,7 @@ class PrivilegedEuiccChannelManager(context: Context): DefaultEuiccChannelManage
}
override fun notifyEuiccProfilesChanged(logicalSlotId: Int) {
(context.applicationContext as OpenEuiccApplication).appContainer.subscriptionManager.apply {
appContainer.subscriptionManager.apply {
findEuiccChannelBySlotBlocking(logicalSlotId)?.let {
tryRefreshCachedEuiccInfo(it.cardId)
}

View file

@ -2,14 +2,19 @@ package im.angry.openeuicc.di
import android.content.Context
import im.angry.openeuicc.core.EuiccChannelManager
import im.angry.openeuicc.core.PrivilegedEuiccChannelFactory
import im.angry.openeuicc.core.PrivilegedEuiccChannelManager
class PrivilegedAppContainer(context: Context) : DefaultAppContainer(context) {
override val euiccChannelManager: EuiccChannelManager by lazy {
PrivilegedEuiccChannelManager(context)
PrivilegedEuiccChannelManager(this, context)
}
override val uiComponentFactory by lazy {
PrivilegedUiComponentFactory()
}
override val euiccChannelFactory by lazy {
PrivilegedEuiccChannelFactory(context)
}
}