Compare commits
2 commits
7c6b4ebee5
...
6c2b1675bd
Author | SHA1 | Date | |
---|---|---|---|
6c2b1675bd | |||
1a69c5294b |
9 changed files with 134 additions and 81 deletions
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,69 +1,41 @@
|
||||||
package im.angry.openeuicc.core
|
package im.angry.openeuicc.core
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.se.omapi.SEService
|
|
||||||
import android.telephony.SubscriptionManager
|
import android.telephony.SubscriptionManager
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import im.angry.openeuicc.OpenEuiccApplication
|
import im.angry.openeuicc.di.AppContainer
|
||||||
import im.angry.openeuicc.util.*
|
import im.angry.openeuicc.util.*
|
||||||
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
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.coroutines.withContext
|
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 {
|
companion object {
|
||||||
const val TAG = "EuiccChannelManager"
|
const val TAG = "EuiccChannelManager"
|
||||||
}
|
}
|
||||||
|
|
||||||
private val channels = mutableListOf<EuiccChannel>()
|
private val channels = mutableListOf<EuiccChannel>()
|
||||||
|
|
||||||
private var seService: SEService? = null
|
|
||||||
|
|
||||||
private val lock = Mutex()
|
private val lock = Mutex()
|
||||||
|
|
||||||
protected val tm by lazy {
|
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>
|
protected open val uiccCards: Collection<UiccCardInfoCompat>
|
||||||
get() = (0..<tm.activeModemCountCompat).map { FakeUiccCardInfoCompat(it) }
|
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? {
|
private suspend fun tryOpenEuiccChannel(port: UiccPortInfoCompat): EuiccChannel? {
|
||||||
lock.withLock {
|
lock.withLock {
|
||||||
ensureSEService()
|
|
||||||
val existing =
|
val existing =
|
||||||
channels.find { it.slotId == port.card.physicalSlotIndex && it.portId == port.portIndex }
|
channels.find { it.slotId == port.card.physicalSlotIndex && it.portId == port.portIndex }
|
||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
|
@ -80,17 +52,9 @@ open class DefaultEuiccChannelManager(protected val context: Context) : EuiccCha
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
var euiccChannel: EuiccChannel? = tryOpenEuiccChannelPrivileged(port)
|
return euiccChannelFactory.tryOpenEuiccChannel(port)?.also {
|
||||||
|
channels.add(it)
|
||||||
if (euiccChannel == null) {
|
|
||||||
euiccChannel = tryOpenEuiccChannelUnprivileged(port)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (euiccChannel != null) {
|
|
||||||
channels.add(euiccChannel)
|
|
||||||
}
|
|
||||||
|
|
||||||
return euiccChannel
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,8 +108,6 @@ open class DefaultEuiccChannelManager(protected val context: Context) : EuiccCha
|
||||||
|
|
||||||
override suspend fun enumerateEuiccChannels() {
|
override suspend fun enumerateEuiccChannels() {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
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) {
|
||||||
|
@ -168,7 +130,6 @@ open class DefaultEuiccChannelManager(protected val context: Context) : EuiccCha
|
||||||
}
|
}
|
||||||
|
|
||||||
channels.clear()
|
channels.clear()
|
||||||
seService?.shutdown()
|
euiccChannelFactory.cleanup()
|
||||||
seService = null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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()
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package im.angry.openeuicc.di
|
||||||
|
|
||||||
import android.telephony.SubscriptionManager
|
import android.telephony.SubscriptionManager
|
||||||
import android.telephony.TelephonyManager
|
import android.telephony.TelephonyManager
|
||||||
|
import im.angry.openeuicc.core.EuiccChannelFactory
|
||||||
import im.angry.openeuicc.core.EuiccChannelManager
|
import im.angry.openeuicc.core.EuiccChannelManager
|
||||||
import im.angry.openeuicc.util.*
|
import im.angry.openeuicc.util.*
|
||||||
|
|
||||||
|
@ -11,4 +12,5 @@ interface AppContainer {
|
||||||
val subscriptionManager: SubscriptionManager
|
val subscriptionManager: SubscriptionManager
|
||||||
val preferenceRepository: PreferenceRepository
|
val preferenceRepository: PreferenceRepository
|
||||||
val uiComponentFactory: UiComponentFactory
|
val uiComponentFactory: UiComponentFactory
|
||||||
|
val euiccChannelFactory: EuiccChannelFactory
|
||||||
}
|
}
|
|
@ -3,6 +3,7 @@ package im.angry.openeuicc.di
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.telephony.SubscriptionManager
|
import android.telephony.SubscriptionManager
|
||||||
import android.telephony.TelephonyManager
|
import android.telephony.TelephonyManager
|
||||||
|
import im.angry.openeuicc.core.DefaultEuiccChannelFactory
|
||||||
import im.angry.openeuicc.core.DefaultEuiccChannelManager
|
import im.angry.openeuicc.core.DefaultEuiccChannelManager
|
||||||
import im.angry.openeuicc.core.EuiccChannelManager
|
import im.angry.openeuicc.core.EuiccChannelManager
|
||||||
import im.angry.openeuicc.util.*
|
import im.angry.openeuicc.util.*
|
||||||
|
@ -13,7 +14,7 @@ open class DefaultAppContainer(context: Context) : AppContainer {
|
||||||
}
|
}
|
||||||
|
|
||||||
override val euiccChannelManager: EuiccChannelManager by lazy {
|
override val euiccChannelManager: EuiccChannelManager by lazy {
|
||||||
DefaultEuiccChannelManager(context)
|
DefaultEuiccChannelManager(this, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val subscriptionManager by lazy {
|
override val subscriptionManager by lazy {
|
||||||
|
@ -27,4 +28,8 @@ open class DefaultAppContainer(context: Context) : AppContainer {
|
||||||
override val uiComponentFactory by lazy {
|
override val uiComponentFactory by lazy {
|
||||||
DefaultUiComponentFactory()
|
DefaultUiComponentFactory()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val euiccChannelFactory by lazy {
|
||||||
|
DefaultEuiccChannelFactory(context)
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -14,7 +14,7 @@ import kotlinx.coroutines.flow.map
|
||||||
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "prefs")
|
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "prefs")
|
||||||
|
|
||||||
val Context.preferenceRepository: PreferenceRepository
|
val Context.preferenceRepository: PreferenceRepository
|
||||||
get() = (applicationContext as OpenEuiccApplication).preferenceRepository
|
get() = (applicationContext as OpenEuiccApplication).appContainer.preferenceRepository
|
||||||
|
|
||||||
val Fragment.preferenceRepository: PreferenceRepository
|
val Fragment.preferenceRepository: PreferenceRepository
|
||||||
get() = requireContext().preferenceRepository
|
get() = requireContext().preferenceRepository
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,37 +1,15 @@
|
||||||
package im.angry.openeuicc.core
|
package im.angry.openeuicc.core
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Log
|
import im.angry.openeuicc.di.AppContainer
|
||||||
import im.angry.openeuicc.OpenEuiccApplication
|
|
||||||
import im.angry.openeuicc.util.*
|
import im.angry.openeuicc.util.*
|
||||||
import java.lang.Exception
|
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>
|
override val uiccCards: Collection<UiccCardInfoCompat>
|
||||||
get() = tm.uiccCardsInfoCompat
|
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
|
// Clean up channels left open in TelephonyManager
|
||||||
// due to a (potentially) forced restart
|
// due to a (potentially) forced restart
|
||||||
// This should be called every time the application is restarted
|
// This should be called every time the application is restarted
|
||||||
|
@ -48,7 +26,7 @@ class PrivilegedEuiccChannelManager(context: Context): DefaultEuiccChannelManage
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun notifyEuiccProfilesChanged(logicalSlotId: Int) {
|
override fun notifyEuiccProfilesChanged(logicalSlotId: Int) {
|
||||||
(context.applicationContext as OpenEuiccApplication).appContainer.subscriptionManager.apply {
|
appContainer.subscriptionManager.apply {
|
||||||
findEuiccChannelBySlotBlocking(logicalSlotId)?.let {
|
findEuiccChannelBySlotBlocking(logicalSlotId)?.let {
|
||||||
tryRefreshCachedEuiccInfo(it.cardId)
|
tryRefreshCachedEuiccInfo(it.cardId)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,19 @@ package im.angry.openeuicc.di
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import im.angry.openeuicc.core.EuiccChannelManager
|
import im.angry.openeuicc.core.EuiccChannelManager
|
||||||
|
import im.angry.openeuicc.core.PrivilegedEuiccChannelFactory
|
||||||
import im.angry.openeuicc.core.PrivilegedEuiccChannelManager
|
import im.angry.openeuicc.core.PrivilegedEuiccChannelManager
|
||||||
|
|
||||||
class PrivilegedAppContainer(context: Context) : DefaultAppContainer(context) {
|
class PrivilegedAppContainer(context: Context) : DefaultAppContainer(context) {
|
||||||
override val euiccChannelManager: EuiccChannelManager by lazy {
|
override val euiccChannelManager: EuiccChannelManager by lazy {
|
||||||
PrivilegedEuiccChannelManager(context)
|
PrivilegedEuiccChannelManager(this, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val uiComponentFactory by lazy {
|
override val uiComponentFactory by lazy {
|
||||||
PrivilegedUiComponentFactory()
|
PrivilegedUiComponentFactory()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val euiccChannelFactory by lazy {
|
||||||
|
PrivilegedEuiccChannelFactory(context)
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Add table
Reference in a new issue