WIP: feat: customizable max segment size [NOT MERGE] #124
5 changed files with 71 additions and 15 deletions
|
@ -10,7 +10,11 @@ import im.angry.openeuicc.common.R
|
||||||
import im.angry.openeuicc.core.usb.UsbApduInterface
|
import im.angry.openeuicc.core.usb.UsbApduInterface
|
||||||
import im.angry.openeuicc.core.usb.getIoEndpoints
|
import im.angry.openeuicc.core.usb.getIoEndpoints
|
||||||
import im.angry.openeuicc.util.*
|
import im.angry.openeuicc.util.*
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import java.lang.IllegalArgumentException
|
import java.lang.IllegalArgumentException
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccChannelFactory {
|
open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccChannelFactory {
|
||||||
private var seService: SEService? = null
|
private var seService: SEService? = null
|
||||||
|
@ -33,21 +37,23 @@ open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccCha
|
||||||
ensureSEService()
|
ensureSEService()
|
||||||
|
|
||||||
Log.i(DefaultEuiccChannelManager.TAG, "Trying OMAPI for physical slot ${port.card.physicalSlotIndex}")
|
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 {
|
try {
|
||||||
return EuiccChannelImpl(
|
return EuiccChannelImpl(
|
||||||
context.getString(R.string.omapi),
|
context.getString(R.string.omapi),
|
||||||
port,
|
port,
|
||||||
intrinsicChannelName = null,
|
intrinsicChannelName = null,
|
||||||
OmapiApduInterface(
|
OmapiApduInterface(seService!!, port, verboseLoggingFlow),
|
||||||
seService!!,
|
verboseLoggingFlow,
|
||||||
port,
|
ignoreTLSCertificateFlow,
|
||||||
context.preferenceRepository.verboseLoggingFlow
|
|
||||||
),
|
|
||||||
context.preferenceRepository.verboseLoggingFlow,
|
|
||||||
context.preferenceRepository.ignoreTLSCertificateFlow,
|
|
||||||
).also {
|
).also {
|
||||||
Log.i(DefaultEuiccChannelManager.TAG, "Is OMAPI channel, setting MSS to 60")
|
val mss = runBlocking { maxSegmentSizeFlow.first() }.toByte()
|
||||||
it.lpa.setEs10xMss(60)
|
Log.i(DefaultEuiccChannelManager.TAG, "Is OMAPI channel, setting MSS to $mss")
|
||||||
|
it.lpa.setEs10xMss(mss)
|
||||||
}
|
}
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
// Failed
|
// Failed
|
||||||
|
|
|
@ -5,21 +5,26 @@ import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
|
import android.text.InputType
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.preference.CheckBoxPreference
|
import androidx.preference.CheckBoxPreference
|
||||||
|
import androidx.preference.EditTextPreference
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceCategory
|
import androidx.preference.PreferenceCategory
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
import im.angry.openeuicc.common.R
|
import im.angry.openeuicc.common.R
|
||||||
import im.angry.openeuicc.util.*
|
import im.angry.openeuicc.util.*
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
open class SettingsFragment: PreferenceFragmentCompat() {
|
open class SettingsFragment: PreferenceFragmentCompat() {
|
||||||
private lateinit var developerPref: PreferenceCategory
|
private val developerPref by lazy {
|
||||||
|
requirePreference<PreferenceCategory>("pref_developer")
|
||||||
|
}
|
||||||
|
|
||||||
// Hidden developer options switch
|
// Hidden developer options switch
|
||||||
private var numClicks = 0
|
private var numClicks = 0
|
||||||
|
@ -29,10 +34,8 @@ open class SettingsFragment: PreferenceFragmentCompat() {
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
setPreferencesFromResource(R.xml.pref_settings, rootKey)
|
setPreferencesFromResource(R.xml.pref_settings, rootKey)
|
||||||
|
|
||||||
developerPref = requirePreference("pref_developer")
|
|
||||||
|
|
||||||
// Show / hide developer preference based on whether it is enabled
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
|
// Show / hide developer preference based on whether it is enabled
|
||||||
preferenceRepository.developerOptionsEnabledFlow
|
preferenceRepository.developerOptionsEnabledFlow
|
||||||
.onEach { developerPref.isVisible = it }
|
.onEach { developerPref.isVisible = it }
|
||||||
.collect()
|
.collect()
|
||||||
|
@ -57,6 +60,36 @@ open class SettingsFragment: PreferenceFragmentCompat() {
|
||||||
intent = Intent(requireContext(), LogsActivity::class.java)
|
intent = Intent(requireContext(), LogsActivity::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
findPreference<EditTextPreference>("pref_developer_max_segment_size")?.apply {
|
||||||
|
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)
|
||||||
|
// 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.
|
||||||
|
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 {
|
||||||
|
it.inputType = InputType.TYPE_CLASS_NUMBER
|
||||||
|
it.selectAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
requirePreference<CheckBoxPreference>("pref_notifications_download")
|
requirePreference<CheckBoxPreference>("pref_notifications_download")
|
||||||
.bindBooleanFlow(preferenceRepository.notificationDownloadFlow)
|
.bindBooleanFlow(preferenceRepository.notificationDownloadFlow)
|
||||||
|
|
||||||
|
@ -136,8 +169,8 @@ open class SettingsFragment: PreferenceFragmentCompat() {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun mergePreferenceOverlay(overlayKey: String, targetKey: String) {
|
protected fun mergePreferenceOverlay(overlayKey: String, targetKey: String) {
|
||||||
val overlayCat = requirePreference<PreferenceCategory>(overlayKey)
|
val overlayCat = findPreference<PreferenceCategory>(overlayKey)!!
|
||||||
val targetCat = requirePreference<PreferenceCategory>(targetKey)
|
val targetCat = findPreference<PreferenceCategory>(targetKey)!!
|
||||||
|
|
||||||
val prefs = buildList {
|
val prefs = buildList {
|
||||||
for (i in 0..<overlayCat.preferenceCount) {
|
for (i in 0..<overlayCat.preferenceCount) {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import androidx.datastore.core.DataStore
|
||||||
import androidx.datastore.preferences.core.Preferences
|
import androidx.datastore.preferences.core.Preferences
|
||||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||||
import androidx.datastore.preferences.core.edit
|
import androidx.datastore.preferences.core.edit
|
||||||
|
import androidx.datastore.preferences.core.intPreferencesKey
|
||||||
import androidx.datastore.preferences.preferencesDataStore
|
import androidx.datastore.preferences.preferencesDataStore
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import im.angry.openeuicc.OpenEuiccApplication
|
import im.angry.openeuicc.OpenEuiccApplication
|
||||||
|
@ -33,6 +34,7 @@ internal object PreferenceKeys {
|
||||||
val DEVELOPER_OPTIONS_ENABLED = booleanPreferencesKey("developer_options_enabled")
|
val DEVELOPER_OPTIONS_ENABLED = booleanPreferencesKey("developer_options_enabled")
|
||||||
val UNFILTERED_PROFILE_LIST = booleanPreferencesKey("unfiltered_profile_list")
|
val UNFILTERED_PROFILE_LIST = booleanPreferencesKey("unfiltered_profile_list")
|
||||||
val IGNORE_TLS_CERTIFICATE = booleanPreferencesKey("ignore_tls_certificate")
|
val IGNORE_TLS_CERTIFICATE = booleanPreferencesKey("ignore_tls_certificate")
|
||||||
|
val MAX_SEGMENT_SIZE = intPreferencesKey("max_segment_size")
|
||||||
}
|
}
|
||||||
|
|
||||||
class PreferenceRepository(private val context: Context) {
|
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 developerOptionsEnabledFlow = bindFlow(PreferenceKeys.DEVELOPER_OPTIONS_ENABLED, false)
|
||||||
val unfilteredProfileListFlow = bindFlow(PreferenceKeys.UNFILTERED_PROFILE_LIST, false)
|
val unfilteredProfileListFlow = bindFlow(PreferenceKeys.UNFILTERED_PROFILE_LIST, false)
|
||||||
val ignoreTLSCertificateFlow = bindFlow(PreferenceKeys.IGNORE_TLS_CERTIFICATE, false)
|
val ignoreTLSCertificateFlow = bindFlow(PreferenceKeys.IGNORE_TLS_CERTIFICATE, false)
|
||||||
|
val maxSegmentSizeFlow = bindFlow(PreferenceKeys.MAX_SEGMENT_SIZE, 60)
|
||||||
|
|
||||||
private fun <T> bindFlow(key: Preferences.Key<T>, defaultValue: T): PreferenceFlowWrapper<T> =
|
private fun <T> bindFlow(key: Preferences.Key<T>, defaultValue: T): PreferenceFlowWrapper<T> =
|
||||||
PreferenceFlowWrapper(context, key, defaultValue)
|
PreferenceFlowWrapper(context, key, defaultValue)
|
||||||
|
@ -66,6 +69,10 @@ class PreferenceFlowWrapper<T> private constructor(
|
||||||
context.dataStore.data.map { it[key] ?: defaultValue }
|
context.dataStore.data.map { it[key] ?: defaultValue }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
suspend fun removePreference() {
|
||||||
|
context.dataStore.edit { it.remove(key) }
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun updatePreference(value: T) {
|
suspend fun updatePreference(value: T) {
|
||||||
context.dataStore.edit { it[key] = value }
|
context.dataStore.edit { it[key] = value }
|
||||||
}
|
}
|
||||||
|
|
|
@ -164,6 +164,9 @@
|
||||||
<string name="pref_developer_unfiltered_profile_list_desc">Include non-production profiles in the list</string>
|
<string name="pref_developer_unfiltered_profile_list_desc">Include non-production profiles in the list</string>
|
||||||
<string name="pref_developer_ignore_tls_certificate">Ignore SM-DP+ TLS certificate</string>
|
<string name="pref_developer_ignore_tls_certificate">Ignore SM-DP+ TLS certificate</string>
|
||||||
<string name="pref_developer_ignore_tls_certificate_desc">Accept any TLS certificate used by the RSP server</string>
|
<string name="pref_developer_ignore_tls_certificate_desc">Accept any TLS certificate used by the RSP server</string>
|
||||||
|
<string name="pref_developer_mss">Max Segment Size</string>
|
||||||
|
<string name="pref_developer_mss_desc">Update Max Segment Size</string>
|
||||||
|
<string name="pref_developer_mss_dialog_message">The valid max segment value range is from 32 to 255.</string>
|
||||||
<string name="pref_info">Info</string>
|
<string name="pref_info">Info</string>
|
||||||
<string name="pref_info_app_version">App Version</string>
|
<string name="pref_info_app_version">App Version</string>
|
||||||
<string name="pref_info_source_code">Source Code</string>
|
<string name="pref_info_source_code">Source Code</string>
|
||||||
|
|
|
@ -69,6 +69,13 @@
|
||||||
app:summary="@string/pref_developer_ignore_tls_certificate_desc"
|
app:summary="@string/pref_developer_ignore_tls_certificate_desc"
|
||||||
app:title="@string/pref_developer_ignore_tls_certificate" />
|
app:title="@string/pref_developer_ignore_tls_certificate" />
|
||||||
|
|
||||||
|
<EditTextPreference
|
||||||
|
app:dialogMessage="@string/pref_developer_mss_dialog_message"
|
||||||
|
app:iconSpaceReserved="false"
|
||||||
|
app:key="pref_developer_max_segment_size"
|
||||||
|
app:summary="@string/pref_developer_mss_desc"
|
||||||
|
app:title="@string/pref_developer_mss" />
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
|
|
Loading…
Add table
Reference in a new issue