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 deleted file mode 100644 index 15e0191..0000000 --- a/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelFactory.kt +++ /dev/null @@ -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 - } -} \ 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 deleted file mode 100644 index a978677..0000000 --- a/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelManager.kt +++ /dev/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() - - 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 deleted file mode 100644 index c8435dc..0000000 --- a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelFactory.kt +++ /dev/null @@ -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() -} \ 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 b0bce1d..9033d34 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,47 +1,174 @@ package im.angry.openeuicc.core -interface EuiccChannelManager { - val knownChannels: List +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 - /** - * Scan all possible sources for EuiccChannels and have them cached for future use - */ - suspend fun enumerateEuiccChannels() +open class EuiccChannelManager(protected val context: Context) : IEuiccChannelManager { + companion object { + const val TAG = "EuiccChannelManager" + } - /** - * Returns the EuiccChannel corresponding to a **logical** slot - */ - fun findEuiccChannelBySlotBlocking(logicalSlotId: Int): EuiccChannel? + private val channels = mutableListOf() - /** - * 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 var seService: SEService? = null - /** - * Returns all EuiccChannels corresponding to a **physical** slot - * Multiple channels are possible in the case of MEP - */ - fun findAllEuiccChannelsByPhysicalSlotBlocking(physicalSlotId: Int): List? + private val lock = Mutex() - /** - * Returns the EuiccChannel corresponding to a **physical** slot and a port ID - */ - fun findEuiccChannelByPortBlocking(physicalSlotId: Int, portId: Int): EuiccChannel? + protected val tm by lazy { + (context.applicationContext as OpenEuiccApplication).appContainer.telephonyManager + } - /** - * Invalidate all EuiccChannels previously known by this Manager - */ - fun invalidate() + 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 } } \ 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 new file mode 100644 index 0000000..8a6ab54 --- /dev/null +++ b/app-common/src/main/java/im/angry/openeuicc/core/IEuiccChannelManager.kt @@ -0,0 +1,47 @@ +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 6ad6e0d..65c2aa9 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,15 +2,12 @@ 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.core.IEuiccChannelManager import im.angry.openeuicc.util.* interface AppContainer { val telephonyManager: TelephonyManager - val euiccChannelManager: EuiccChannelManager + val euiccChannelManager: IEuiccChannelManager 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 7a1a3fe..dd7826c 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,9 +3,8 @@ 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 { @@ -13,8 +12,8 @@ open class DefaultAppContainer(context: Context) : AppContainer { context.getSystemService(TelephonyManager::class.java)!! } - override val euiccChannelManager: EuiccChannelManager by lazy { - DefaultEuiccChannelManager(this, context) + override val euiccChannelManager: IEuiccChannelManager by lazy { + EuiccChannelManager(context) } override val subscriptionManager by lazy { @@ -24,12 +23,4 @@ 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 deleted file mode 100644 index 86af007..0000000 --- a/app-common/src/main/java/im/angry/openeuicc/di/DefaultUiComponentFactory.kt +++ /dev/null @@ -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) -} \ 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 deleted file mode 100644 index d311876..0000000 --- a/app-common/src/main/java/im/angry/openeuicc/di/UiComponentFactory.kt +++ /dev/null @@ -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 -} \ 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 70f6992..1a7e50f 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,15 +23,13 @@ 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(logStr.encodeToByteArray()) + os.write(logText.text.toString().encodeToByteArray()) } } } @@ -78,12 +76,7 @@ class LogsActivity : AppCompatActivity() { private suspend fun reload() = withContext(Dispatchers.Main) { swipeRefresh.isRefreshing = true - 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") - } + logText.text = intent.extras?.getString("log") ?: readSelfLog() 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 ca6715d..af6cbf4 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,6 +88,10 @@ 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() @@ -104,7 +108,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(appContainer.uiComponentFactory.createEuiccManagementFragment(channel)) + fragments.add(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 b9a854e..accbbb5 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).appContainer.preferenceRepository + get() = (applicationContext as OpenEuiccApplication).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 33699d7..57626e2 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.EuiccChannelManager +import im.angry.openeuicc.core.IEuiccChannelManager 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: EuiccChannelManager + val euiccChannelManager: IEuiccChannelManager 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 ae5a4da..3d9c47b 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,13 +47,11 @@ 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 } @@ -111,11 +109,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_UNKNOWN + return State.FAILURE } 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(", ")) - return State.SUCCESS + return State.FAILURE } return State.SUCCESS @@ -134,13 +132,7 @@ internal class IsdrChannelAccessCheck(private val context: Context): Compatibili override suspend fun doCheck(): State { val seService = connectSEService(context) - 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 { + val (validSlotIds, result) = seService.readers.filter { it.isSIM }.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 588c073..3a20af1 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. 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. + Your device has no support for accessing SIM cards via OMAPI. 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. 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. + Unable to detect Secure Element readers for SIM cards via OMAPI. + Only the following SIM slots are accessible via OMAPI: %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 deleted file mode 100644 index 78acefe..0000000 --- a/app/src/main/java/im/angry/openeuicc/core/PrivilegedEuiccChannelFactory.kt +++ /dev/null @@ -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) - } -} \ 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 1c5d132..75b2b22 100644 --- a/app/src/main/java/im/angry/openeuicc/core/PrivilegedEuiccChannelManager.kt +++ b/app/src/main/java/im/angry/openeuicc/core/PrivilegedEuiccChannelManager.kt @@ -1,15 +1,37 @@ package im.angry.openeuicc.core 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 java.lang.Exception +import java.lang.IllegalArgumentException -class PrivilegedEuiccChannelManager(appContainer: AppContainer, context: Context) : - DefaultEuiccChannelManager(appContainer, context) { +class PrivilegedEuiccChannelManager(context: Context): EuiccChannelManager(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 @@ -26,7 +48,7 @@ class PrivilegedEuiccChannelManager(appContainer: AppContainer, context: Context } override fun notifyEuiccProfilesChanged(logicalSlotId: Int) { - appContainer.subscriptionManager.apply { + (context.applicationContext as OpenEuiccApplication).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 5158352..a849a27 100644 --- a/app/src/main/java/im/angry/openeuicc/di/PrivilegedAppContainer.kt +++ b/app/src/main/java/im/angry/openeuicc/di/PrivilegedAppContainer.kt @@ -1,20 +1,11 @@ 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.IEuiccChannelManager import im.angry.openeuicc.core.PrivilegedEuiccChannelManager class PrivilegedAppContainer(context: Context) : DefaultAppContainer(context) { - override val euiccChannelManager: EuiccChannelManager by lazy { - PrivilegedEuiccChannelManager(this, context) - } - - override val uiComponentFactory by lazy { - PrivilegedUiComponentFactory() - } - - override val euiccChannelFactory by lazy { - PrivilegedEuiccChannelFactory(context) + override val euiccChannelManager: IEuiccChannelManager by lazy { + PrivilegedEuiccChannelManager(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 deleted file mode 100644 index d3c5cdb..0000000 --- a/app/src/main/java/im/angry/openeuicc/di/PrivilegedUiComponentFactory.kt +++ /dev/null @@ -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) -} \ 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 f3c2b3c..1261932 100644 --- a/app/src/main/java/im/angry/openeuicc/ui/PrivilegedMainActivity.kt +++ b/app/src/main/java/im/angry/openeuicc/ui/PrivilegedMainActivity.kt @@ -4,6 +4,7 @@ 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() { @@ -36,4 +37,7 @@ 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 4675ab9..4cb4932 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.EuiccChannelManager +import im.angry.openeuicc.core.IEuiccChannelManager 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: EuiccChannelManager, enabled: Boolean) { +fun TelephonyManager.setDsdsEnabled(euiccManager: IEuiccChannelManager, enabled: Boolean) { runBlocking { euiccManager.enumerateEuiccChannels() } @@ -32,7 +32,7 @@ fun TelephonyManager.setDsdsEnabled(euiccManager: EuiccChannelManager, enabled: // Disable eSIM profiles before switching the slot mapping // This ensures that unmapped eSIM ports never have "ghost" profiles enabled fun TelephonyManager.updateSimSlotMapping( - euiccManager: EuiccChannelManager, newMapping: Collection, + euiccManager: IEuiccChannelManager, 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 13848b6..f3e5e90 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)