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 new file mode 100644 index 0000000..15e0191 --- /dev/null +++ b/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelFactory.kt @@ -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 + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..a978677 --- /dev/null +++ b/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelManager.kt @@ -0,0 +1,135 @@ +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() + + private val lock = Mutex() + + protected val tm by lazy { + appContainer.telephonyManager + } + + private val euiccChannelFactory by lazy { + appContainer.euiccChannelFactory + } + + protected open val uiccCards: Collection + get() = (0..? = + 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 + get() = channels.toList() + + override fun invalidate() { + for (channel in channels) { + channel.close() + } + + channels.clear() + euiccChannelFactory.cleanup() + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..c8435dc --- /dev/null +++ b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelFactory.kt @@ -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() +} \ No newline at end of file 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 9033d34..b0bce1d 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 @@ -1,174 +1,47 @@ 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.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 +interface EuiccChannelManager { + val knownChannels: List -open class EuiccChannelManager(protected val context: Context) : IEuiccChannelManager { - companion object { - const val TAG = "EuiccChannelManager" - } + /** + * Scan all possible sources for EuiccChannels and have them cached for future use + */ + suspend fun enumerateEuiccChannels() - private val channels = mutableListOf() + /** + * 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? - protected val tm by lazy { - (context.applicationContext as OpenEuiccApplication).appContainer.telephonyManager - } + /** + * Returns the EuiccChannel corresponding to a **physical** slot and a port ID + */ + fun findEuiccChannelByPortBlocking(physicalSlotId: Int, portId: Int): EuiccChannel? - protected open val uiccCards: Collection - get() = (0..? = - 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 - get() = channels.toList() - - override fun invalidate() { - for (channel in channels) { - channel.close() - } - - channels.clear() - seService?.shutdown() - seService = null + /** + * 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 } } \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/core/IEuiccChannelManager.kt b/app-common/src/main/java/im/angry/openeuicc/core/IEuiccChannelManager.kt deleted file mode 100644 index 8a6ab54..0000000 --- a/app-common/src/main/java/im/angry/openeuicc/core/IEuiccChannelManager.kt +++ /dev/null @@ -1,47 +0,0 @@ -package im.angry.openeuicc.core - -interface IEuiccChannelManager { - val knownChannels: List - - /** - * 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? - - /** - * 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 - } -} \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/di/AppContainer.kt b/app-common/src/main/java/im/angry/openeuicc/di/AppContainer.kt index 65c2aa9..6ad6e0d 100644 --- a/app-common/src/main/java/im/angry/openeuicc/di/AppContainer.kt +++ b/app-common/src/main/java/im/angry/openeuicc/di/AppContainer.kt @@ -2,12 +2,15 @@ package im.angry.openeuicc.di import android.telephony.SubscriptionManager import android.telephony.TelephonyManager -import im.angry.openeuicc.core.IEuiccChannelManager +import im.angry.openeuicc.core.EuiccChannelFactory +import im.angry.openeuicc.core.EuiccChannelManager import im.angry.openeuicc.util.* interface AppContainer { val telephonyManager: TelephonyManager - val euiccChannelManager: IEuiccChannelManager + val euiccChannelManager: EuiccChannelManager val subscriptionManager: SubscriptionManager val preferenceRepository: PreferenceRepository + val uiComponentFactory: UiComponentFactory + val euiccChannelFactory: EuiccChannelFactory } \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/di/DefaultAppContainer.kt b/app-common/src/main/java/im/angry/openeuicc/di/DefaultAppContainer.kt index dd7826c..7a1a3fe 100644 --- a/app-common/src/main/java/im/angry/openeuicc/di/DefaultAppContainer.kt +++ b/app-common/src/main/java/im/angry/openeuicc/di/DefaultAppContainer.kt @@ -3,8 +3,9 @@ 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.core.IEuiccChannelManager import im.angry.openeuicc.util.* open class DefaultAppContainer(context: Context) : AppContainer { @@ -12,8 +13,8 @@ open class DefaultAppContainer(context: Context) : AppContainer { context.getSystemService(TelephonyManager::class.java)!! } - override val euiccChannelManager: IEuiccChannelManager by lazy { - EuiccChannelManager(context) + override val euiccChannelManager: EuiccChannelManager by lazy { + DefaultEuiccChannelManager(this, context) } override val subscriptionManager by lazy { @@ -23,4 +24,12 @@ open class DefaultAppContainer(context: Context) : AppContainer { override val preferenceRepository by lazy { PreferenceRepository(context) } + + override val uiComponentFactory by lazy { + DefaultUiComponentFactory() + } + + override val euiccChannelFactory by lazy { + DefaultEuiccChannelFactory(context) + } } \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/di/DefaultUiComponentFactory.kt b/app-common/src/main/java/im/angry/openeuicc/di/DefaultUiComponentFactory.kt new file mode 100644 index 0000000..86af007 --- /dev/null +++ b/app-common/src/main/java/im/angry/openeuicc/di/DefaultUiComponentFactory.kt @@ -0,0 +1,9 @@ +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) +} \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/di/UiComponentFactory.kt b/app-common/src/main/java/im/angry/openeuicc/di/UiComponentFactory.kt new file mode 100644 index 0000000..d311876 --- /dev/null +++ b/app-common/src/main/java/im/angry/openeuicc/di/UiComponentFactory.kt @@ -0,0 +1,8 @@ +package im.angry.openeuicc.di + +import im.angry.openeuicc.core.EuiccChannel +import im.angry.openeuicc.ui.EuiccManagementFragment + +interface UiComponentFactory { + fun createEuiccManagementFragment(channel: EuiccChannel): EuiccManagementFragment +} \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/LogsActivity.kt b/app-common/src/main/java/im/angry/openeuicc/ui/LogsActivity.kt index 1a7e50f..70f6992 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/LogsActivity.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/LogsActivity.kt @@ -23,13 +23,15 @@ class LogsActivity : AppCompatActivity() { private lateinit var swipeRefresh: SwipeRefreshLayout private lateinit var scrollView: ScrollView private lateinit var logText: TextView + private lateinit var logStr: String private val saveLogs = registerForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) { uri -> if (uri == null) return@registerForActivityResult + if (!this::logStr.isInitialized) return@registerForActivityResult contentResolver.openFileDescriptor(uri, "w")?.use { FileOutputStream(it.fileDescriptor).use { os -> - os.write(logText.text.toString().encodeToByteArray()) + os.write(logStr.encodeToByteArray()) } } } @@ -76,7 +78,12 @@ class LogsActivity : AppCompatActivity() { private suspend fun reload() = withContext(Dispatchers.Main) { swipeRefresh.isRefreshing = true - logText.text = intent.extras?.getString("log") ?: readSelfLog() + logStr = 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 diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/MainActivity.kt b/app-common/src/main/java/im/angry/openeuicc/ui/MainActivity.kt index af6cbf4..ca6715d 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/MainActivity.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/MainActivity.kt @@ -88,10 +88,6 @@ open class MainActivity : AppCompatActivity(), OpenEuiccContextMarker { else -> super.onOptionsItemSelected(item) } - - protected open fun createEuiccManagementFragment(channel: EuiccChannel): EuiccManagementFragment = - EuiccManagementFragment.newInstance(channel.slotId, channel.portId) - private suspend fun init() { withContext(Dispatchers.IO) { euiccChannelManager.enumerateEuiccChannels() @@ -108,7 +104,7 @@ open class MainActivity : AppCompatActivity(), OpenEuiccContextMarker { withContext(Dispatchers.Main) { euiccChannelManager.knownChannels.sortedBy { it.logicalSlotId }.forEach { channel -> spinnerAdapter.add(getString(R.string.channel_name_format, channel.logicalSlotId)) - fragments.add(createEuiccManagementFragment(channel)) + fragments.add(appContainer.uiComponentFactory.createEuiccManagementFragment(channel)) } if (fragments.isNotEmpty()) { diff --git a/app-common/src/main/java/im/angry/openeuicc/util/PreferenceUtils.kt b/app-common/src/main/java/im/angry/openeuicc/util/PreferenceUtils.kt index accbbb5..b9a854e 100644 --- a/app-common/src/main/java/im/angry/openeuicc/util/PreferenceUtils.kt +++ b/app-common/src/main/java/im/angry/openeuicc/util/PreferenceUtils.kt @@ -14,7 +14,7 @@ import kotlinx.coroutines.flow.map private val Context.dataStore: DataStore by preferencesDataStore(name = "prefs") val Context.preferenceRepository: PreferenceRepository - get() = (applicationContext as OpenEuiccApplication).preferenceRepository + get() = (applicationContext as OpenEuiccApplication).appContainer.preferenceRepository val Fragment.preferenceRepository: PreferenceRepository get() = requireContext().preferenceRepository diff --git a/app-common/src/main/java/im/angry/openeuicc/util/Utils.kt b/app-common/src/main/java/im/angry/openeuicc/util/Utils.kt index 57626e2..33699d7 100644 --- a/app-common/src/main/java/im/angry/openeuicc/util/Utils.kt +++ b/app-common/src/main/java/im/angry/openeuicc/util/Utils.kt @@ -7,7 +7,7 @@ import android.telephony.TelephonyManager import androidx.fragment.app.Fragment import im.angry.openeuicc.OpenEuiccApplication import im.angry.openeuicc.core.EuiccChannel -import im.angry.openeuicc.core.IEuiccChannelManager +import im.angry.openeuicc.core.EuiccChannelManager import im.angry.openeuicc.di.AppContainer import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking @@ -52,7 +52,7 @@ interface OpenEuiccContextMarker { val appContainer: AppContainer get() = openEuiccApplication.appContainer - val euiccChannelManager: IEuiccChannelManager + val euiccChannelManager: EuiccChannelManager get() = appContainer.euiccChannelManager val telephonyManager: TelephonyManager diff --git a/app-unpriv/src/main/java/im/angry/openeuicc/util/CompatibilityCheck.kt b/app-unpriv/src/main/java/im/angry/openeuicc/util/CompatibilityCheck.kt index 3d9c47b..ae5a4da 100644 --- a/app-unpriv/src/main/java/im/angry/openeuicc/util/CompatibilityCheck.kt +++ b/app-unpriv/src/main/java/im/angry/openeuicc/util/CompatibilityCheck.kt @@ -47,11 +47,13 @@ abstract class CompatibilityCheck(context: Context) { abstract val title: String protected abstract val defaultDescription: String + protected lateinit var successDescription: String protected lateinit var failureDescription: String val description: String get() = when { (state == State.FAILURE || state == State.FAILURE_UNKNOWN) && this::failureDescription.isInitialized -> failureDescription + state == State.SUCCESS && this::successDescription.isInitialized -> successDescription else -> defaultDescription } @@ -109,11 +111,11 @@ internal class OmapiConnCheck(private val context: Context): CompatibilityCheck( val simReaders = seService.readers.filter { it.isSIM } if (simReaders.isEmpty()) { failureDescription = context.getString(R.string.compatibility_check_omapi_connectivity_fail) - return State.FAILURE + return State.FAILURE_UNKNOWN } else if (simReaders.size < tm.activeModemCountCompat) { - failureDescription = context.getString(R.string.compatibility_check_omapi_connectivity_fail_sim_number, + successDescription = context.getString(R.string.compatibility_check_omapi_connectivity_partial_success_sim_number, simReaders.map { it.slotIndex }.joinToString(", ")) - return State.FAILURE + return State.SUCCESS } return State.SUCCESS @@ -132,7 +134,13 @@ internal class IsdrChannelAccessCheck(private val context: Context): Compatibili override suspend fun doCheck(): State { val seService = connectSEService(context) - val (validSlotIds, result) = seService.readers.filter { it.isSIM }.map { + val readers = seService.readers.filter { it.isSIM } + if (readers.isEmpty()) { + failureDescription = context.getString(R.string.compatibility_check_isdr_channel_desc_unknown) + return State.FAILURE_UNKNOWN + } + + val (validSlotIds, result) = readers.map { try { it.openSession().openLogicalChannel(ISDR_AID)?.close() Pair(it.slotIndex, State.SUCCESS) diff --git a/app-unpriv/src/main/res/values/strings.xml b/app-unpriv/src/main/res/values/strings.xml index 3a20af1..588c073 100644 --- a/app-unpriv/src/main/res/values/strings.xml +++ b/app-unpriv/src/main/res/values/strings.xml @@ -6,11 +6,11 @@ System Features Whether your device has all the required features for managing removable eUICC cards. For example, basic telephony and OMAPI support. Your device has no telephony features. - Your device has no support for accessing SIM cards via 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. OMAPI Connectivity Does your device allow access to Secure Elements on SIM cards via OMAPI? - Unable to detect Secure Element readers for SIM cards via OMAPI. - Only the following SIM slots are accessible via OMAPI: %s. + 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. + Successfully detected Secure Element access, but only for the following SIM slots: %s. ISD-R Channel Access Does your device support opening an ISD-R (management) channel to eSIMs via OMAPI? 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. diff --git a/app/src/main/java/im/angry/openeuicc/core/PrivilegedEuiccChannelFactory.kt b/app/src/main/java/im/angry/openeuicc/core/PrivilegedEuiccChannelFactory.kt new file mode 100644 index 0000000..78acefe --- /dev/null +++ b/app/src/main/java/im/angry/openeuicc/core/PrivilegedEuiccChannelFactory.kt @@ -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) + } +} \ No newline at end of file diff --git a/app/src/main/java/im/angry/openeuicc/core/PrivilegedEuiccChannelManager.kt b/app/src/main/java/im/angry/openeuicc/core/PrivilegedEuiccChannelManager.kt index 75b2b22..1c5d132 100644 --- a/app/src/main/java/im/angry/openeuicc/core/PrivilegedEuiccChannelManager.kt +++ b/app/src/main/java/im/angry/openeuicc/core/PrivilegedEuiccChannelManager.kt @@ -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): EuiccChannelManager(context) { +class PrivilegedEuiccChannelManager(appContainer: AppContainer, context: Context) : + DefaultEuiccChannelManager(appContainer, context) { override val uiccCards: Collection 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): EuiccChannelManager(conte } override fun notifyEuiccProfilesChanged(logicalSlotId: Int) { - (context.applicationContext as OpenEuiccApplication).appContainer.subscriptionManager.apply { + appContainer.subscriptionManager.apply { findEuiccChannelBySlotBlocking(logicalSlotId)?.let { tryRefreshCachedEuiccInfo(it.cardId) } diff --git a/app/src/main/java/im/angry/openeuicc/di/PrivilegedAppContainer.kt b/app/src/main/java/im/angry/openeuicc/di/PrivilegedAppContainer.kt index a849a27..5158352 100644 --- a/app/src/main/java/im/angry/openeuicc/di/PrivilegedAppContainer.kt +++ b/app/src/main/java/im/angry/openeuicc/di/PrivilegedAppContainer.kt @@ -1,11 +1,20 @@ package im.angry.openeuicc.di import android.content.Context -import im.angry.openeuicc.core.IEuiccChannelManager +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: IEuiccChannelManager by lazy { - PrivilegedEuiccChannelManager(context) + override val euiccChannelManager: EuiccChannelManager by lazy { + PrivilegedEuiccChannelManager(this, context) + } + + override val uiComponentFactory by lazy { + PrivilegedUiComponentFactory() + } + + override val euiccChannelFactory by lazy { + PrivilegedEuiccChannelFactory(context) } } \ No newline at end of file diff --git a/app/src/main/java/im/angry/openeuicc/di/PrivilegedUiComponentFactory.kt b/app/src/main/java/im/angry/openeuicc/di/PrivilegedUiComponentFactory.kt new file mode 100644 index 0000000..d3c5cdb --- /dev/null +++ b/app/src/main/java/im/angry/openeuicc/di/PrivilegedUiComponentFactory.kt @@ -0,0 +1,10 @@ +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) +} \ No newline at end of file diff --git a/app/src/main/java/im/angry/openeuicc/ui/PrivilegedMainActivity.kt b/app/src/main/java/im/angry/openeuicc/ui/PrivilegedMainActivity.kt index 1261932..f3c2b3c 100644 --- a/app/src/main/java/im/angry/openeuicc/ui/PrivilegedMainActivity.kt +++ b/app/src/main/java/im/angry/openeuicc/ui/PrivilegedMainActivity.kt @@ -4,7 +4,6 @@ import android.view.Menu import android.view.MenuItem import android.widget.Toast import im.angry.openeuicc.R -import im.angry.openeuicc.core.EuiccChannel import im.angry.openeuicc.util.* class PrivilegedMainActivity : MainActivity() { @@ -37,7 +36,4 @@ class PrivilegedMainActivity : MainActivity() { } else -> super.onOptionsItemSelected(item) } - - override fun createEuiccManagementFragment(channel: EuiccChannel): EuiccManagementFragment = - PrivilegedEuiccManagementFragment.newInstance(channel.slotId, channel.portId) } \ No newline at end of file diff --git a/app/src/main/java/im/angry/openeuicc/util/PrivilegedTelephonyUtils.kt b/app/src/main/java/im/angry/openeuicc/util/PrivilegedTelephonyUtils.kt index 4cb4932..4675ab9 100644 --- a/app/src/main/java/im/angry/openeuicc/util/PrivilegedTelephonyUtils.kt +++ b/app/src/main/java/im/angry/openeuicc/util/PrivilegedTelephonyUtils.kt @@ -4,7 +4,7 @@ import android.telephony.SubscriptionManager import android.telephony.TelephonyManager import android.telephony.UiccSlotMapping import im.angry.openeuicc.core.EuiccChannel -import im.angry.openeuicc.core.IEuiccChannelManager +import im.angry.openeuicc.core.EuiccChannelManager import kotlinx.coroutines.runBlocking import java.lang.Exception @@ -14,7 +14,7 @@ val TelephonyManager.supportsDSDS: Boolean val TelephonyManager.dsdsEnabled: Boolean get() = activeModemCount >= 2 -fun TelephonyManager.setDsdsEnabled(euiccManager: IEuiccChannelManager, enabled: Boolean) { +fun TelephonyManager.setDsdsEnabled(euiccManager: EuiccChannelManager, enabled: Boolean) { runBlocking { euiccManager.enumerateEuiccChannels() } @@ -32,7 +32,7 @@ fun TelephonyManager.setDsdsEnabled(euiccManager: IEuiccChannelManager, enabled: // Disable eSIM profiles before switching the slot mapping // This ensures that unmapped eSIM ports never have "ghost" profiles enabled fun TelephonyManager.updateSimSlotMapping( - euiccManager: IEuiccChannelManager, newMapping: Collection, + euiccManager: EuiccChannelManager, newMapping: Collection, currentMapping: Collection = simSlotMapping ) { val unmapped = currentMapping.filterNot { mapping -> diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/RootCertificates.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/RootCertificates.kt index f3e5e90..13848b6 100644 --- a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/RootCertificates.kt +++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/RootCertificates.kt @@ -8,7 +8,7 @@ import java.security.cert.CertificateFactory const val DEFAULT_PKID_GSMA_RSP2_ROOT_CI1 = "81370f5125d0b1d408d4c3b232e6d25e795bebfb" private fun getCertificate(keyId: String): Certificate? = - KNOWN_CI_CERTS[keyId]?.toByteArray().let { cert -> + KNOWN_CI_CERTS[keyId]?.toByteArray()?.let { cert -> ByteArrayInputStream(cert).use { stream -> val cf = CertificateFactory.getInstance("X.509") cf.generateCertificate(stream)