Compare commits
No commits in common. "ca0085e14733906ad20474b8246633b91df272b3" and "2d1c96023a8669036dc2652e13414bcb4f05a893" have entirely different histories.
ca0085e147
...
2d1c96023a
22 changed files with 269 additions and 365 deletions
|
@ -1,43 +0,0 @@
|
||||||
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,135 +0,0 @@
|
||||||
package im.angry.openeuicc.core
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.telephony.SubscriptionManager
|
|
||||||
import android.util.Log
|
|
||||||
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
|
|
||||||
|
|
||||||
open class DefaultEuiccChannelManager(
|
|
||||||
protected val appContainer: AppContainer,
|
|
||||||
protected val context: Context
|
|
||||||
) : EuiccChannelManager {
|
|
||||||
companion object {
|
|
||||||
const val TAG = "EuiccChannelManager"
|
|
||||||
}
|
|
||||||
|
|
||||||
private val channels = mutableListOf<EuiccChannel>()
|
|
||||||
|
|
||||||
private val lock = Mutex()
|
|
||||||
|
|
||||||
protected val tm by lazy {
|
|
||||||
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 tryOpenEuiccChannel(port: UiccPortInfoCompat): EuiccChannel? {
|
|
||||||
lock.withLock {
|
|
||||||
val existing =
|
|
||||||
channels.find { it.slotId == port.card.physicalSlotIndex && it.portId == port.portIndex }
|
|
||||||
if (existing != null) {
|
|
||||||
if (existing.valid && port.logicalSlotIndex == existing.logicalSlotId) {
|
|
||||||
return existing
|
|
||||||
} else {
|
|
||||||
existing.close()
|
|
||||||
channels.remove(existing)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (port.logicalSlotIndex == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
|
|
||||||
// We can only open channels on ports that are actually enabled
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return euiccChannelFactory.tryOpenEuiccChannel(port)?.also {
|
|
||||||
channels.add(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun findEuiccChannelBySlotBlocking(logicalSlotId: Int): EuiccChannel? =
|
|
||||||
runBlocking {
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
for (card in uiccCards) {
|
|
||||||
for (port in card.ports) {
|
|
||||||
if (port.logicalSlotIndex == logicalSlotId) {
|
|
||||||
return@withContext tryOpenEuiccChannel(port)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun findEuiccChannelByPhysicalSlotBlocking(physicalSlotId: Int): EuiccChannel? =
|
|
||||||
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) {
|
|
||||||
if (card.physicalSlotIndex != physicalSlotId) continue
|
|
||||||
return@runBlocking card.ports.mapNotNull { tryOpenEuiccChannel(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) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun enumerateEuiccChannels() {
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
for (uiccInfo in uiccCards) {
|
|
||||||
for (port in uiccInfo.ports) {
|
|
||||||
if (tryOpenEuiccChannel(port) != null) {
|
|
||||||
Log.d(
|
|
||||||
TAG,
|
|
||||||
"Found eUICC on slot ${uiccInfo.physicalSlotIndex} port ${port.portIndex}"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override val knownChannels: List<EuiccChannel>
|
|
||||||
get() = channels.toList()
|
|
||||||
|
|
||||||
override fun invalidate() {
|
|
||||||
for (channel in channels) {
|
|
||||||
channel.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
channels.clear()
|
|
||||||
euiccChannelFactory.cleanup()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
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()
|
|
||||||
}
|
|
|
@ -1,47 +1,174 @@
|
||||||
package im.angry.openeuicc.core
|
package im.angry.openeuicc.core
|
||||||
|
|
||||||
interface EuiccChannelManager {
|
import android.content.Context
|
||||||
val knownChannels: List<EuiccChannel>
|
import android.se.omapi.SEService
|
||||||
|
import android.telephony.SubscriptionManager
|
||||||
|
import android.util.Log
|
||||||
|
import im.angry.openeuicc.OpenEuiccApplication
|
||||||
|
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 EuiccChannelManager(protected val context: Context) : IEuiccChannelManager {
|
||||||
* Scan all possible sources for EuiccChannels and have them cached for future use
|
companion object {
|
||||||
*/
|
const val TAG = "EuiccChannelManager"
|
||||||
suspend fun enumerateEuiccChannels()
|
}
|
||||||
|
|
||||||
/**
|
private val channels = mutableListOf<EuiccChannel>()
|
||||||
* Returns the EuiccChannel corresponding to a **logical** slot
|
|
||||||
*/
|
|
||||||
fun findEuiccChannelBySlotBlocking(logicalSlotId: Int): EuiccChannel?
|
|
||||||
|
|
||||||
/**
|
private var seService: SEService? = null
|
||||||
* 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?
|
|
||||||
|
|
||||||
/**
|
private val lock = Mutex()
|
||||||
* Returns all EuiccChannels corresponding to a **physical** slot
|
|
||||||
* Multiple channels are possible in the case of MEP
|
|
||||||
*/
|
|
||||||
fun findAllEuiccChannelsByPhysicalSlotBlocking(physicalSlotId: Int): List<EuiccChannel>?
|
|
||||||
|
|
||||||
/**
|
protected val tm by lazy {
|
||||||
* Returns the EuiccChannel corresponding to a **physical** slot and a port ID
|
(context.applicationContext as OpenEuiccApplication).appContainer.telephonyManager
|
||||||
*/
|
}
|
||||||
fun findEuiccChannelByPortBlocking(physicalSlotId: Int, portId: Int): EuiccChannel?
|
|
||||||
|
|
||||||
/**
|
protected open val uiccCards: Collection<UiccCardInfoCompat>
|
||||||
* Invalidate all EuiccChannels previously known by this Manager
|
get() = (0..<tm.activeModemCountCompat).map { FakeUiccCardInfoCompat(it) }
|
||||||
*/
|
|
||||||
fun invalidate()
|
|
||||||
|
|
||||||
/**
|
private suspend fun ensureSEService() {
|
||||||
* If possible, trigger the system to update the cached list of profiles
|
if (seService == null) {
|
||||||
* This is only expected to be implemented when the application is privileged
|
seService = connectSEService(context)
|
||||||
* TODO: Remove this from the common interface
|
}
|
||||||
*/
|
}
|
||||||
fun notifyEuiccProfilesChanged(logicalSlotId: Int) {
|
|
||||||
// no-op by default
|
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) {
|
||||||
|
if (existing.valid && port.logicalSlotIndex == existing.logicalSlotId) {
|
||||||
|
return existing
|
||||||
|
} else {
|
||||||
|
existing.close()
|
||||||
|
channels.remove(existing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (port.logicalSlotIndex == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
|
||||||
|
// We can only open channels on ports that are actually enabled
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
var euiccChannel: EuiccChannel? = tryOpenEuiccChannelPrivileged(port)
|
||||||
|
|
||||||
|
if (euiccChannel == null) {
|
||||||
|
euiccChannel = tryOpenEuiccChannelUnprivileged(port)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (euiccChannel != null) {
|
||||||
|
channels.add(euiccChannel)
|
||||||
|
}
|
||||||
|
|
||||||
|
return euiccChannel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findEuiccChannelBySlotBlocking(logicalSlotId: Int): EuiccChannel? =
|
||||||
|
runBlocking {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
for (card in uiccCards) {
|
||||||
|
for (port in card.ports) {
|
||||||
|
if (port.logicalSlotIndex == logicalSlotId) {
|
||||||
|
return@withContext tryOpenEuiccChannel(port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findEuiccChannelByPhysicalSlotBlocking(physicalSlotId: Int): EuiccChannel? =
|
||||||
|
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) {
|
||||||
|
if (card.physicalSlotIndex != physicalSlotId) continue
|
||||||
|
return@runBlocking card.ports.mapNotNull { tryOpenEuiccChannel(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) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun enumerateEuiccChannels() {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
ensureSEService()
|
||||||
|
|
||||||
|
for (uiccInfo in uiccCards) {
|
||||||
|
for (port in uiccInfo.ports) {
|
||||||
|
if (tryOpenEuiccChannel(port) != null) {
|
||||||
|
Log.d(
|
||||||
|
TAG,
|
||||||
|
"Found eUICC on slot ${uiccInfo.physicalSlotIndex} port ${port.portIndex}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val knownChannels: List<EuiccChannel>
|
||||||
|
get() = channels.toList()
|
||||||
|
|
||||||
|
override fun invalidate() {
|
||||||
|
for (channel in channels) {
|
||||||
|
channel.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
channels.clear()
|
||||||
|
seService?.shutdown()
|
||||||
|
seService = null
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,15 +2,12 @@ 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.IEuiccChannelManager
|
||||||
import im.angry.openeuicc.core.EuiccChannelManager
|
|
||||||
import im.angry.openeuicc.util.*
|
import im.angry.openeuicc.util.*
|
||||||
|
|
||||||
interface AppContainer {
|
interface AppContainer {
|
||||||
val telephonyManager: TelephonyManager
|
val telephonyManager: TelephonyManager
|
||||||
val euiccChannelManager: EuiccChannelManager
|
val euiccChannelManager: IEuiccChannelManager
|
||||||
val subscriptionManager: SubscriptionManager
|
val subscriptionManager: SubscriptionManager
|
||||||
val preferenceRepository: PreferenceRepository
|
val preferenceRepository: PreferenceRepository
|
||||||
val uiComponentFactory: UiComponentFactory
|
|
||||||
val euiccChannelFactory: EuiccChannelFactory
|
|
||||||
}
|
}
|
|
@ -3,9 +3,8 @@ 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.EuiccChannelManager
|
import im.angry.openeuicc.core.EuiccChannelManager
|
||||||
|
import im.angry.openeuicc.core.IEuiccChannelManager
|
||||||
import im.angry.openeuicc.util.*
|
import im.angry.openeuicc.util.*
|
||||||
|
|
||||||
open class DefaultAppContainer(context: Context) : AppContainer {
|
open class DefaultAppContainer(context: Context) : AppContainer {
|
||||||
|
@ -13,8 +12,8 @@ open class DefaultAppContainer(context: Context) : AppContainer {
|
||||||
context.getSystemService(TelephonyManager::class.java)!!
|
context.getSystemService(TelephonyManager::class.java)!!
|
||||||
}
|
}
|
||||||
|
|
||||||
override val euiccChannelManager: EuiccChannelManager by lazy {
|
override val euiccChannelManager: IEuiccChannelManager by lazy {
|
||||||
DefaultEuiccChannelManager(this, context)
|
EuiccChannelManager(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val subscriptionManager by lazy {
|
override val subscriptionManager by lazy {
|
||||||
|
@ -24,12 +23,4 @@ open class DefaultAppContainer(context: Context) : AppContainer {
|
||||||
override val preferenceRepository by lazy {
|
override val preferenceRepository by lazy {
|
||||||
PreferenceRepository(context)
|
PreferenceRepository(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val uiComponentFactory by lazy {
|
|
||||||
DefaultUiComponentFactory()
|
|
||||||
}
|
|
||||||
|
|
||||||
override val euiccChannelFactory by lazy {
|
|
||||||
DefaultEuiccChannelFactory(context)
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,9 +0,0 @@
|
||||||
package im.angry.openeuicc.di
|
|
||||||
|
|
||||||
import im.angry.openeuicc.core.EuiccChannel
|
|
||||||
import im.angry.openeuicc.ui.EuiccManagementFragment
|
|
||||||
|
|
||||||
open class DefaultUiComponentFactory : UiComponentFactory {
|
|
||||||
override fun createEuiccManagementFragment(channel: EuiccChannel): EuiccManagementFragment =
|
|
||||||
EuiccManagementFragment.newInstance(channel.slotId, channel.portId)
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
package im.angry.openeuicc.di
|
|
||||||
|
|
||||||
import im.angry.openeuicc.core.EuiccChannel
|
|
||||||
import im.angry.openeuicc.ui.EuiccManagementFragment
|
|
||||||
|
|
||||||
interface UiComponentFactory {
|
|
||||||
fun createEuiccManagementFragment(channel: EuiccChannel): EuiccManagementFragment
|
|
||||||
}
|
|
|
@ -23,15 +23,13 @@ class LogsActivity : AppCompatActivity() {
|
||||||
private lateinit var swipeRefresh: SwipeRefreshLayout
|
private lateinit var swipeRefresh: SwipeRefreshLayout
|
||||||
private lateinit var scrollView: ScrollView
|
private lateinit var scrollView: ScrollView
|
||||||
private lateinit var logText: TextView
|
private lateinit var logText: TextView
|
||||||
private lateinit var logStr: String
|
|
||||||
|
|
||||||
private val saveLogs =
|
private val saveLogs =
|
||||||
registerForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) { uri ->
|
registerForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) { uri ->
|
||||||
if (uri == null) return@registerForActivityResult
|
if (uri == null) return@registerForActivityResult
|
||||||
if (!this::logStr.isInitialized) return@registerForActivityResult
|
|
||||||
contentResolver.openFileDescriptor(uri, "w")?.use {
|
contentResolver.openFileDescriptor(uri, "w")?.use {
|
||||||
FileOutputStream(it.fileDescriptor).use { os ->
|
FileOutputStream(it.fileDescriptor).use { os ->
|
||||||
os.write(logStr.encodeToByteArray())
|
os.write(logText.text.toString().encodeToByteArray())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,12 +76,7 @@ class LogsActivity : AppCompatActivity() {
|
||||||
private suspend fun reload() = withContext(Dispatchers.Main) {
|
private suspend fun reload() = withContext(Dispatchers.Main) {
|
||||||
swipeRefresh.isRefreshing = true
|
swipeRefresh.isRefreshing = true
|
||||||
|
|
||||||
logStr = intent.extras?.getString("log") ?: readSelfLog()
|
logText.text = intent.extras?.getString("log") ?: readSelfLog()
|
||||||
|
|
||||||
logText.text = withContext(Dispatchers.IO) {
|
|
||||||
// Limit the UI to display only 256 lines
|
|
||||||
logStr.lines().takeLast(256).joinToString("\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
swipeRefresh.isRefreshing = false
|
swipeRefresh.isRefreshing = false
|
||||||
|
|
||||||
|
|
|
@ -88,6 +88,10 @@ open class MainActivity : AppCompatActivity(), OpenEuiccContextMarker {
|
||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected open fun createEuiccManagementFragment(channel: EuiccChannel): EuiccManagementFragment =
|
||||||
|
EuiccManagementFragment.newInstance(channel.slotId, channel.portId)
|
||||||
|
|
||||||
private suspend fun init() {
|
private suspend fun init() {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
euiccChannelManager.enumerateEuiccChannels()
|
euiccChannelManager.enumerateEuiccChannels()
|
||||||
|
@ -104,7 +108,7 @@ open class MainActivity : AppCompatActivity(), OpenEuiccContextMarker {
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
euiccChannelManager.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(appContainer.uiComponentFactory.createEuiccManagementFragment(channel))
|
fragments.add(createEuiccManagementFragment(channel))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fragments.isNotEmpty()) {
|
if (fragments.isNotEmpty()) {
|
||||||
|
|
|
@ -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).appContainer.preferenceRepository
|
get() = (applicationContext as OpenEuiccApplication).preferenceRepository
|
||||||
|
|
||||||
val Fragment.preferenceRepository: PreferenceRepository
|
val Fragment.preferenceRepository: PreferenceRepository
|
||||||
get() = requireContext().preferenceRepository
|
get() = requireContext().preferenceRepository
|
||||||
|
|
|
@ -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 im.angry.openeuicc.di.AppContainer
|
import im.angry.openeuicc.di.AppContainer
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
@ -52,7 +52,7 @@ interface OpenEuiccContextMarker {
|
||||||
val appContainer: AppContainer
|
val appContainer: AppContainer
|
||||||
get() = openEuiccApplication.appContainer
|
get() = openEuiccApplication.appContainer
|
||||||
|
|
||||||
val euiccChannelManager: EuiccChannelManager
|
val euiccChannelManager: IEuiccChannelManager
|
||||||
get() = appContainer.euiccChannelManager
|
get() = appContainer.euiccChannelManager
|
||||||
|
|
||||||
val telephonyManager: TelephonyManager
|
val telephonyManager: TelephonyManager
|
||||||
|
|
|
@ -47,13 +47,11 @@ abstract class CompatibilityCheck(context: Context) {
|
||||||
|
|
||||||
abstract val title: String
|
abstract val title: String
|
||||||
protected abstract val defaultDescription: String
|
protected abstract val defaultDescription: String
|
||||||
protected lateinit var successDescription: String
|
|
||||||
protected lateinit var failureDescription: String
|
protected lateinit var failureDescription: String
|
||||||
|
|
||||||
val description: String
|
val description: String
|
||||||
get() = when {
|
get() = when {
|
||||||
(state == State.FAILURE || state == State.FAILURE_UNKNOWN) && this::failureDescription.isInitialized -> failureDescription
|
(state == State.FAILURE || state == State.FAILURE_UNKNOWN) && this::failureDescription.isInitialized -> failureDescription
|
||||||
state == State.SUCCESS && this::successDescription.isInitialized -> successDescription
|
|
||||||
else -> defaultDescription
|
else -> defaultDescription
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,11 +109,11 @@ internal class OmapiConnCheck(private val context: Context): CompatibilityCheck(
|
||||||
val simReaders = seService.readers.filter { it.isSIM }
|
val simReaders = seService.readers.filter { it.isSIM }
|
||||||
if (simReaders.isEmpty()) {
|
if (simReaders.isEmpty()) {
|
||||||
failureDescription = context.getString(R.string.compatibility_check_omapi_connectivity_fail)
|
failureDescription = context.getString(R.string.compatibility_check_omapi_connectivity_fail)
|
||||||
return State.FAILURE_UNKNOWN
|
return State.FAILURE
|
||||||
} else if (simReaders.size < tm.activeModemCountCompat) {
|
} else if (simReaders.size < tm.activeModemCountCompat) {
|
||||||
successDescription = context.getString(R.string.compatibility_check_omapi_connectivity_partial_success_sim_number,
|
failureDescription = context.getString(R.string.compatibility_check_omapi_connectivity_fail_sim_number,
|
||||||
simReaders.map { it.slotIndex }.joinToString(", "))
|
simReaders.map { it.slotIndex }.joinToString(", "))
|
||||||
return State.SUCCESS
|
return State.FAILURE
|
||||||
}
|
}
|
||||||
|
|
||||||
return State.SUCCESS
|
return State.SUCCESS
|
||||||
|
@ -134,13 +132,7 @@ internal class IsdrChannelAccessCheck(private val context: Context): Compatibili
|
||||||
|
|
||||||
override suspend fun doCheck(): State {
|
override suspend fun doCheck(): State {
|
||||||
val seService = connectSEService(context)
|
val seService = connectSEService(context)
|
||||||
val readers = seService.readers.filter { it.isSIM }
|
val (validSlotIds, result) = seService.readers.filter { it.isSIM }.map {
|
||||||
if (readers.isEmpty()) {
|
|
||||||
failureDescription = context.getString(R.string.compatibility_check_isdr_channel_desc_unknown)
|
|
||||||
return State.FAILURE_UNKNOWN
|
|
||||||
}
|
|
||||||
|
|
||||||
val (validSlotIds, result) = readers.map {
|
|
||||||
try {
|
try {
|
||||||
it.openSession().openLogicalChannel(ISDR_AID)?.close()
|
it.openSession().openLogicalChannel(ISDR_AID)?.close()
|
||||||
Pair(it.slotIndex, State.SUCCESS)
|
Pair(it.slotIndex, State.SUCCESS)
|
||||||
|
|
|
@ -6,11 +6,11 @@
|
||||||
<string name="compatibility_check_system_features">System Features</string>
|
<string name="compatibility_check_system_features">System Features</string>
|
||||||
<string name="compatibility_check_system_features_desc">Whether your device has all the required features for managing removable eUICC cards. For example, basic telephony and OMAPI support.</string>
|
<string name="compatibility_check_system_features_desc">Whether your device has all the required features for managing removable eUICC cards. For example, basic telephony and OMAPI support.</string>
|
||||||
<string name="compatibility_check_system_features_no_telephony">Your device has no telephony features.</string>
|
<string name="compatibility_check_system_features_no_telephony">Your device has no telephony features.</string>
|
||||||
<string name="compatibility_check_system_features_no_omapi">Your device has no support for accessing SIM cards via OMAPI. If you are using a custom ROM, consider contacting the developer to determine whether it is due to hardware or a missing feature declaration in the OS.</string>
|
<string name="compatibility_check_system_features_no_omapi">Your device has no support for accessing SIM cards via OMAPI.</string>
|
||||||
<string name="compatibility_check_omapi_connectivity">OMAPI Connectivity</string>
|
<string name="compatibility_check_omapi_connectivity">OMAPI Connectivity</string>
|
||||||
<string name="compatibility_check_omapi_connectivity_desc">Does your device allow access to Secure Elements on SIM cards via OMAPI?</string>
|
<string name="compatibility_check_omapi_connectivity_desc">Does your device allow access to Secure Elements on SIM cards via OMAPI?</string>
|
||||||
<string name="compatibility_check_omapi_connectivity_fail">Unable to detect Secure Element readers for SIM cards via OMAPI. If you have not inserted a SIM in this device, try inserting one and retry this check.</string>
|
<string name="compatibility_check_omapi_connectivity_fail">Unable to detect Secure Element readers for SIM cards via OMAPI.</string>
|
||||||
<string name="compatibility_check_omapi_connectivity_partial_success_sim_number">Successfully detected Secure Element access, but only for the following SIM slots: %s.</string>
|
<string name="compatibility_check_omapi_connectivity_fail_sim_number">Only the following SIM slots are accessible via OMAPI: %s.</string>
|
||||||
<string name="compatibility_check_isdr_channel">ISD-R Channel Access</string>
|
<string name="compatibility_check_isdr_channel">ISD-R Channel Access</string>
|
||||||
<string name="compatibility_check_isdr_channel_desc">Does your device support opening an ISD-R (management) channel to eSIMs via OMAPI?</string>
|
<string name="compatibility_check_isdr_channel_desc">Does your device support opening an ISD-R (management) channel to eSIMs via OMAPI?</string>
|
||||||
<string name="compatibility_check_isdr_channel_desc_unknown">Cannot determine whether ISD-R access through OMAPI is supported. You might want to retry with SIM cards inserted (any SIM card will do) if not already.</string>
|
<string name="compatibility_check_isdr_channel_desc_unknown">Cannot determine whether ISD-R access through OMAPI is supported. You might want to retry with SIM cards inserted (any SIM card will do) if not already.</string>
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
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,15 +1,37 @@
|
||||||
package im.angry.openeuicc.core
|
package im.angry.openeuicc.core
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import im.angry.openeuicc.di.AppContainer
|
import android.util.Log
|
||||||
|
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(appContainer: AppContainer, context: Context) :
|
class PrivilegedEuiccChannelManager(context: Context): EuiccChannelManager(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
|
||||||
|
@ -26,7 +48,7 @@ class PrivilegedEuiccChannelManager(appContainer: AppContainer, context: Context
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun notifyEuiccProfilesChanged(logicalSlotId: Int) {
|
override fun notifyEuiccProfilesChanged(logicalSlotId: Int) {
|
||||||
appContainer.subscriptionManager.apply {
|
(context.applicationContext as OpenEuiccApplication).appContainer.subscriptionManager.apply {
|
||||||
findEuiccChannelBySlotBlocking(logicalSlotId)?.let {
|
findEuiccChannelBySlotBlocking(logicalSlotId)?.let {
|
||||||
tryRefreshCachedEuiccInfo(it.cardId)
|
tryRefreshCachedEuiccInfo(it.cardId)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,11 @@
|
||||||
package im.angry.openeuicc.di
|
package im.angry.openeuicc.di
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import im.angry.openeuicc.core.EuiccChannelManager
|
import im.angry.openeuicc.core.IEuiccChannelManager
|
||||||
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: IEuiccChannelManager by lazy {
|
||||||
PrivilegedEuiccChannelManager(this, context)
|
PrivilegedEuiccChannelManager(context)
|
||||||
}
|
|
||||||
|
|
||||||
override val uiComponentFactory by lazy {
|
|
||||||
PrivilegedUiComponentFactory()
|
|
||||||
}
|
|
||||||
|
|
||||||
override val euiccChannelFactory by lazy {
|
|
||||||
PrivilegedEuiccChannelFactory(context)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,10 +0,0 @@
|
||||||
package im.angry.openeuicc.di
|
|
||||||
|
|
||||||
import im.angry.openeuicc.core.EuiccChannel
|
|
||||||
import im.angry.openeuicc.ui.EuiccManagementFragment
|
|
||||||
import im.angry.openeuicc.ui.PrivilegedEuiccManagementFragment
|
|
||||||
|
|
||||||
class PrivilegedUiComponentFactory : DefaultUiComponentFactory() {
|
|
||||||
override fun createEuiccManagementFragment(channel: EuiccChannel): EuiccManagementFragment =
|
|
||||||
PrivilegedEuiccManagementFragment.newInstance(channel.slotId, channel.portId)
|
|
||||||
}
|
|
|
@ -4,6 +4,7 @@ import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import im.angry.openeuicc.R
|
import im.angry.openeuicc.R
|
||||||
|
import im.angry.openeuicc.core.EuiccChannel
|
||||||
import im.angry.openeuicc.util.*
|
import im.angry.openeuicc.util.*
|
||||||
|
|
||||||
class PrivilegedMainActivity : MainActivity() {
|
class PrivilegedMainActivity : MainActivity() {
|
||||||
|
@ -36,4 +37,7 @@ class PrivilegedMainActivity : MainActivity() {
|
||||||
}
|
}
|
||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun createEuiccManagementFragment(channel: EuiccChannel): EuiccManagementFragment =
|
||||||
|
PrivilegedEuiccManagementFragment.newInstance(channel.slotId, channel.portId)
|
||||||
}
|
}
|
|
@ -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 ->
|
||||||
|
|
|
@ -8,7 +8,7 @@ import java.security.cert.CertificateFactory
|
||||||
const val DEFAULT_PKID_GSMA_RSP2_ROOT_CI1 = "81370f5125d0b1d408d4c3b232e6d25e795bebfb"
|
const val DEFAULT_PKID_GSMA_RSP2_ROOT_CI1 = "81370f5125d0b1d408d4c3b232e6d25e795bebfb"
|
||||||
|
|
||||||
private fun getCertificate(keyId: String): Certificate? =
|
private fun getCertificate(keyId: String): Certificate? =
|
||||||
KNOWN_CI_CERTS[keyId]?.toByteArray()?.let { cert ->
|
KNOWN_CI_CERTS[keyId]?.toByteArray().let { cert ->
|
||||||
ByteArrayInputStream(cert).use { stream ->
|
ByteArrayInputStream(cert).use { stream ->
|
||||||
val cf = CertificateFactory.getInstance("X.509")
|
val cf = CertificateFactory.getInstance("X.509")
|
||||||
cf.generateCertificate(stream)
|
cf.generateCertificate(stream)
|
||||||
|
|
Loading…
Add table
Reference in a new issue