From 738e8aedfb0aec851497cb781642ef83ec1a4429 Mon Sep 17 00:00:00 2001 From: septs Date: Mon, 16 Dec 2024 18:34:19 +0800 Subject: [PATCH 1/3] feat: customizable max segment size --- .../core/DefaultEuiccChannelFactory.kt | 32 +++++++++++++------ .../angry/openeuicc/util/PreferenceUtils.kt | 3 ++ 2 files changed, 26 insertions(+), 9 deletions(-) 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 index 5e87564..5b1600e 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelFactory.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelFactory.kt @@ -10,7 +10,11 @@ import im.angry.openeuicc.common.R import im.angry.openeuicc.core.usb.UsbApduInterface import im.angry.openeuicc.core.usb.getIoEndpoints import im.angry.openeuicc.util.* +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.runBlocking import java.lang.IllegalArgumentException +import kotlin.math.max +import kotlin.math.min open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccChannelFactory { private var seService: SEService? = null @@ -33,21 +37,31 @@ open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccCha ensureSEService() Log.i(DefaultEuiccChannelManager.TAG, "Trying OMAPI for physical slot ${port.card.physicalSlotIndex}") + + val verboseLoggingFlow = context.preferenceRepository.verboseLoggingFlow + val ignoreTLSCertificateFlow = context.preferenceRepository.ignoreTLSCertificateFlow + val maxSegmentSizeFlow = context.preferenceRepository.maxSegmentSizeFlow + try { return EuiccChannelImpl( context.getString(R.string.omapi), port, intrinsicChannelName = null, - OmapiApduInterface( - seService!!, - port, - context.preferenceRepository.verboseLoggingFlow - ), - context.preferenceRepository.verboseLoggingFlow, - context.preferenceRepository.ignoreTLSCertificateFlow, + OmapiApduInterface(seService!!, port, verboseLoggingFlow), + verboseLoggingFlow, + ignoreTLSCertificateFlow, ).also { - Log.i(DefaultEuiccChannelManager.TAG, "Is OMAPI channel, setting MSS to 60") - it.lpa.setEs10xMss(60) + // SGP.22 v2.2.2, 2.5.5 Segmented Bound Profile Package (Page 33 of 268) + // https://www.gsma.com/solutions-and-impact/technologies/esim/wp-content/uploads/2020/06/SGP.22-v2.2.2.pdf#page=33 + // + // Each segment of this list that is up to 255 bytes is transported in one APDU. + // Larger TLVs are sent in blocks of 255 bytes for the first blocks and a last block that MAY be shorter. + val mss = runBlocking { + // [32, 255] + min(max(maxSegmentSizeFlow.first(), 32), 255) + } + Log.i(DefaultEuiccChannelManager.TAG, "Is OMAPI channel, setting MSS to $mss") + it.lpa.setEs10xMss(mss.toByte()) } } catch (e: IllegalArgumentException) { // Failed 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 f5e3ca2..c78ae41 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 @@ -5,6 +5,7 @@ import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.intPreferencesKey import androidx.datastore.preferences.preferencesDataStore import androidx.fragment.app.Fragment import im.angry.openeuicc.OpenEuiccApplication @@ -33,6 +34,7 @@ internal object PreferenceKeys { val DEVELOPER_OPTIONS_ENABLED = booleanPreferencesKey("developer_options_enabled") val UNFILTERED_PROFILE_LIST = booleanPreferencesKey("unfiltered_profile_list") val IGNORE_TLS_CERTIFICATE = booleanPreferencesKey("ignore_tls_certificate") + val MAX_SEGMENT_SIZE = intPreferencesKey("max_segment_size") } class PreferenceRepository(private val context: Context) { @@ -50,6 +52,7 @@ class PreferenceRepository(private val context: Context) { val developerOptionsEnabledFlow = bindFlow(PreferenceKeys.DEVELOPER_OPTIONS_ENABLED, false) val unfilteredProfileListFlow = bindFlow(PreferenceKeys.UNFILTERED_PROFILE_LIST, false) val ignoreTLSCertificateFlow = bindFlow(PreferenceKeys.IGNORE_TLS_CERTIFICATE, false) + val maxSegmentSizeFlow = bindFlow(PreferenceKeys.MAX_SEGMENT_SIZE, 60) private fun bindFlow(key: Preferences.Key, defaultValue: T): PreferenceFlowWrapper = PreferenceFlowWrapper(context, key, defaultValue) -- 2.45.3 From 7e985d51116d49942a817eba217b8cb657c11446 Mon Sep 17 00:00:00 2001 From: septs Date: Mon, 16 Dec 2024 19:31:53 +0800 Subject: [PATCH 2/3] feat: customizable max segment size --- .../core/DefaultEuiccChannelFactory.kt | 12 +---- .../im/angry/openeuicc/ui/SettingsFragment.kt | 47 +++++++++++++++++-- app-common/src/main/res/values/strings.xml | 3 ++ app-common/src/main/res/xml/pref_settings.xml | 7 +++ 4 files changed, 54 insertions(+), 15 deletions(-) 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 index 5b1600e..446611d 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelFactory.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelFactory.kt @@ -51,17 +51,9 @@ open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccCha verboseLoggingFlow, ignoreTLSCertificateFlow, ).also { - // SGP.22 v2.2.2, 2.5.5 Segmented Bound Profile Package (Page 33 of 268) - // https://www.gsma.com/solutions-and-impact/technologies/esim/wp-content/uploads/2020/06/SGP.22-v2.2.2.pdf#page=33 - // - // Each segment of this list that is up to 255 bytes is transported in one APDU. - // Larger TLVs are sent in blocks of 255 bytes for the first blocks and a last block that MAY be shorter. - val mss = runBlocking { - // [32, 255] - min(max(maxSegmentSizeFlow.first(), 32), 255) - } + val mss = runBlocking { maxSegmentSizeFlow.first() }.toByte() Log.i(DefaultEuiccChannelManager.TAG, "Is OMAPI channel, setting MSS to $mss") - it.lpa.setEs10xMss(mss.toByte()) + it.lpa.setEs10xMss(mss) } } catch (e: IllegalArgumentException) { // Failed diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/SettingsFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/SettingsFragment.kt index ef4ef8b..4f0293a 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/SettingsFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/SettingsFragment.kt @@ -5,21 +5,35 @@ import android.net.Uri import android.os.Build import android.os.Bundle import android.provider.Settings +import android.text.InputFilter +import android.text.InputType +import android.text.Spanned import android.widget.Toast import androidx.lifecycle.lifecycleScope import androidx.preference.CheckBoxPreference +import androidx.preference.EditTextPreference import androidx.preference.Preference import androidx.preference.PreferenceCategory import androidx.preference.PreferenceFragmentCompat import im.angry.openeuicc.common.R -import im.angry.openeuicc.util.* +import im.angry.openeuicc.util.PreferenceFlowWrapper +import im.angry.openeuicc.util.preferenceRepository +import im.angry.openeuicc.util.selfAppVersion +import im.angry.openeuicc.util.setupRootViewInsets import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking + open class SettingsFragment: PreferenceFragmentCompat() { - private lateinit var developerPref: PreferenceCategory + private val developerPref by lazy { + findPreference("pref_developer")!! + } + + private val mss by lazy { + findPreference("pref_developer_max_segment_size")!! + } // Hidden developer options switch private var numClicks = 0 @@ -29,13 +43,15 @@ open class SettingsFragment: PreferenceFragmentCompat() { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.pref_settings, rootKey) - developerPref = findPreference("pref_developer")!! - - // Show / hide developer preference based on whether it is enabled lifecycleScope.launch { + // Show / hide developer preference based on whether it is enabled preferenceRepository.developerOptionsEnabledFlow .onEach { developerPref.isVisible = it } .collect() + // Sync MSS latest value to Preference + preferenceRepository.maxSegmentSizeFlow + .onEach { mss.text = it.toString() } + .collect() } findPreference("pref_info_app_version")?.apply { @@ -57,6 +73,27 @@ open class SettingsFragment: PreferenceFragmentCompat() { intent = Intent(requireContext(), LogsActivity::class.java) } + findPreference("pref_developer_max_segment_size")?.apply { + val setMSS = preferenceRepository.maxSegmentSizeFlow::updatePreference + + setOnPreferenceChangeListener { _, newValue -> + // SGP.22 v2.2.2, 2.5.5 Segmented Bound Profile Package (Page 33 of 268) + // https://www.gsma.com/solutions-and-impact/technologies/esim/wp-content/uploads/2020/06/SGP.22-v2.2.2.pdf#page=33 + // + // Each segment of this list that is up to 255 bytes is transported in one APDU. + // Larger TLVs are sent in blocks of 255 bytes for the first blocks and a last block that MAY be shorter. + val mss = newValue.toString().toInt() + val isValid = mss in 32..255 + if (isValid) runBlocking { setMSS(mss) } + isValid + } + + setOnBindEditTextListener { + it.inputType = InputType.TYPE_CLASS_NUMBER + it.selectAll() + } + } + findPreference("pref_notifications_download") ?.bindBooleanFlow(preferenceRepository.notificationDownloadFlow) diff --git a/app-common/src/main/res/values/strings.xml b/app-common/src/main/res/values/strings.xml index 95ea261..57549d9 100644 --- a/app-common/src/main/res/values/strings.xml +++ b/app-common/src/main/res/values/strings.xml @@ -163,6 +163,9 @@ Include non-production profiles in the list Ignore SM-DP+ TLS certificate Accept any TLS certificate used by the RSP server + Max Segment Size + Update Max Segment Size + The valid max segment value range is from 32 to 255. Info App Version Source Code diff --git a/app-common/src/main/res/xml/pref_settings.xml b/app-common/src/main/res/xml/pref_settings.xml index bb5bd50..63ef08a 100644 --- a/app-common/src/main/res/xml/pref_settings.xml +++ b/app-common/src/main/res/xml/pref_settings.xml @@ -69,6 +69,13 @@ app:summary="@string/pref_developer_ignore_tls_certificate_desc" app:title="@string/pref_developer_ignore_tls_certificate" /> + + Date: Mon, 16 Dec 2024 20:34:58 +0800 Subject: [PATCH 3/3] refactor --- .../im/angry/openeuicc/ui/SettingsFragment.kt | 35 +++++++++---------- .../angry/openeuicc/util/PreferenceUtils.kt | 4 +++ 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/SettingsFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/SettingsFragment.kt index 4f0293a..fa9cb4d 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/SettingsFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/SettingsFragment.kt @@ -5,9 +5,7 @@ import android.net.Uri import android.os.Build import android.os.Bundle import android.provider.Settings -import android.text.InputFilter import android.text.InputType -import android.text.Spanned import android.widget.Toast import androidx.lifecycle.lifecycleScope import androidx.preference.CheckBoxPreference @@ -16,11 +14,9 @@ import androidx.preference.Preference import androidx.preference.PreferenceCategory import androidx.preference.PreferenceFragmentCompat import im.angry.openeuicc.common.R -import im.angry.openeuicc.util.PreferenceFlowWrapper -import im.angry.openeuicc.util.preferenceRepository -import im.angry.openeuicc.util.selfAppVersion -import im.angry.openeuicc.util.setupRootViewInsets +import im.angry.openeuicc.util.* import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @@ -31,10 +27,6 @@ open class SettingsFragment: PreferenceFragmentCompat() { findPreference("pref_developer")!! } - private val mss by lazy { - findPreference("pref_developer_max_segment_size")!! - } - // Hidden developer options switch private var numClicks = 0 private var lastClickTimestamp = -1L @@ -48,10 +40,6 @@ open class SettingsFragment: PreferenceFragmentCompat() { preferenceRepository.developerOptionsEnabledFlow .onEach { developerPref.isVisible = it } .collect() - // Sync MSS latest value to Preference - preferenceRepository.maxSegmentSizeFlow - .onEach { mss.text = it.toString() } - .collect() } findPreference("pref_info_app_version")?.apply { @@ -74,7 +62,11 @@ open class SettingsFragment: PreferenceFragmentCompat() { } findPreference("pref_developer_max_segment_size")?.apply { - val setMSS = preferenceRepository.maxSegmentSizeFlow::updatePreference + val mssFlow = preferenceRepository.maxSegmentSizeFlow + + lifecycleScope.launch { + mssFlow.onEach { text = it.toString() }.collect() + } setOnPreferenceChangeListener { _, newValue -> // SGP.22 v2.2.2, 2.5.5 Segmented Bound Profile Package (Page 33 of 268) @@ -82,10 +74,15 @@ open class SettingsFragment: PreferenceFragmentCompat() { // // Each segment of this list that is up to 255 bytes is transported in one APDU. // Larger TLVs are sent in blocks of 255 bytes for the first blocks and a last block that MAY be shorter. - val mss = newValue.toString().toInt() - val isValid = mss in 32..255 - if (isValid) runBlocking { setMSS(mss) } - isValid + text = runBlocking { + val value = newValue as String + when { + value.isEmpty() -> mssFlow.removePreference() + value.toInt() in 32..255 -> mssFlow.updatePreference(value.toInt()) + } + mssFlow.first().toString() + } + false } setOnBindEditTextListener { 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 c78ae41..14f90ce 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 @@ -69,6 +69,10 @@ class PreferenceFlowWrapper private constructor( context.dataStore.data.map { it[key] ?: defaultValue } ) + suspend fun removePreference() { + context.dataStore.edit { it.remove(key) } + } + suspend fun updatePreference(value: T) { context.dataStore.edit { it[key] = value } } -- 2.45.3