Compare commits

...

3 commits

7 changed files with 46 additions and 26 deletions

View file

@ -11,6 +11,7 @@ import androidx.core.app.NotificationManagerCompat
import androidx.lifecycle.LifecycleService import androidx.lifecycle.LifecycleService
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import im.angry.openeuicc.common.R import im.angry.openeuicc.common.R
import im.angry.openeuicc.core.EuiccChannel
import im.angry.openeuicc.core.EuiccChannelManager import im.angry.openeuicc.core.EuiccChannelManager
import im.angry.openeuicc.util.* import im.angry.openeuicc.util.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -446,30 +447,30 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker {
iccid: String, iccid: String,
enable: Boolean, // Enable or disable the profile indicated in iccid enable: Boolean, // Enable or disable the profile indicated in iccid
reconnectTimeoutMillis: Long = 0 // 0 = do not wait for reconnect reconnectTimeoutMillis: Long = 0 // 0 = do not wait for reconnect
): ForegroundTaskSubscriberFlow = ) =
launchForegroundTask( launchForegroundTask(
getString(R.string.task_profile_switch), getString(R.string.task_profile_switch),
getString(R.string.task_profile_switch_failure), getString(R.string.task_profile_switch_failure),
R.drawable.ic_task_switch R.drawable.ic_task_switch
) { ) {
euiccChannelManager.beginTrackedOperation(slotId, portId) { suspend fun onSwitch(channel: EuiccChannel): Pair<Boolean, Boolean> {
val (res, refreshed) = euiccChannelManager.withEuiccChannel( val refresh = preferenceRepository.refreshAfterSwitchFlow.first()
slotId, val response = channel.lpa.switchProfile(iccid, enable, refresh)
portId if (response || !refresh) return Pair(response, refresh)
) { channel -> // refresh failed, but refresh was requested
if (!channel.lpa.switchProfile(iccid, enable, refresh = true)) {
// Sometimes, we *can* enable or disable the profile, but we cannot // Sometimes, we *can* enable or disable the profile, but we cannot
// send the refresh command to the modem because the profile somehow // send the refresh command to the modem because the profile somehow
// makes the modem "busy". In this case, we can still switch by setting // makes the modem "busy". In this case, we can still switch by setting
// refresh to false, but then the switch cannot take effect until the // refresh to false, but then the switch cannot take effect until the
// user resets the modem manually by toggling airplane mode or rebooting. // user resets the modem manually by toggling airplane mode or rebooting.
Pair(channel.lpa.switchProfile(iccid, enable, refresh = false), false) return Pair(channel.lpa.switchProfile(iccid, enable, refresh = false), false)
} else {
Pair(true, true)
}
} }
if (!res) { euiccChannelManager.beginTrackedOperation(slotId, portId) {
val (response, refreshed) =
euiccChannelManager.withEuiccChannel(slotId, portId, ::onSwitch)
if (!response) {
throw RuntimeException("Could not switch profile") throw RuntimeException("Could not switch profile")
} }

View file

@ -122,7 +122,7 @@ open class SettingsFragment: PreferenceFragmentCompat() {
return true return true
} }
private fun CheckBoxPreference.bindBooleanFlow(flow: PreferenceFlowWrapper<Boolean>) { protected fun CheckBoxPreference.bindBooleanFlow(flow: PreferenceFlowWrapper<Boolean>) {
lifecycleScope.launch { lifecycleScope.launch {
flow.collect { isChecked = it } flow.collect { isChecked = it }
} }

View file

@ -26,6 +26,7 @@ internal object PreferenceKeys {
val NOTIFICATION_SWITCH = booleanPreferencesKey("notification_switch") val NOTIFICATION_SWITCH = booleanPreferencesKey("notification_switch")
// ---- Advanced ---- // ---- Advanced ----
val REFRESH_AFTER_SWITCH = booleanPreferencesKey("refresh_after_switch")
val DISABLE_SAFEGUARD_REMOVABLE_ESIM = booleanPreferencesKey("disable_safeguard_removable_esim") val DISABLE_SAFEGUARD_REMOVABLE_ESIM = booleanPreferencesKey("disable_safeguard_removable_esim")
val VERBOSE_LOGGING = booleanPreferencesKey("verbose_logging") val VERBOSE_LOGGING = booleanPreferencesKey("verbose_logging")
@ -47,6 +48,7 @@ class PreferenceRepository(private val context: Context) {
val verboseLoggingFlow = bindFlow(PreferenceKeys.VERBOSE_LOGGING, false) val verboseLoggingFlow = bindFlow(PreferenceKeys.VERBOSE_LOGGING, false)
// ---- Developer Options ---- // ---- Developer Options ----
val refreshAfterSwitchFlow = bindFlow(PreferenceKeys.REFRESH_AFTER_SWITCH, true)
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)
@ -58,13 +60,10 @@ class PreferenceRepository(private val context: Context) {
class PreferenceFlowWrapper<T> private constructor( class PreferenceFlowWrapper<T> private constructor(
private val context: Context, private val context: Context,
private val key: Preferences.Key<T>, private val key: Preferences.Key<T>,
inner: Flow<T> inner: Flow<T>,
) : Flow<T> by inner { ) : Flow<T> by inner {
internal constructor(context: Context, key: Preferences.Key<T>, defaultValue: T) : this( internal constructor(context: Context, key: Preferences.Key<T>, defaultValue: T) :
context, this(context, key, context.dataStore.data.map { it[key] ?: defaultValue })
key,
context.dataStore.data.map { it[key] ?: defaultValue }
)
suspend fun updatePreference(value: T) { suspend fun updatePreference(value: T) {
context.dataStore.edit { it[key] = value } context.dataStore.edit { it[key] = value }

View file

@ -162,6 +162,8 @@
<string name="pref_notifications_switch">Switching</string> <string name="pref_notifications_switch">Switching</string>
<string name="pref_notifications_switch_desc">Send notifications for <i>switching</i> profiles\nNote that this type of notification is unreliable.</string> <string name="pref_notifications_switch_desc">Send notifications for <i>switching</i> profiles\nNote that this type of notification is unreliable.</string>
<string name="pref_advanced">Advanced</string> <string name="pref_advanced">Advanced</string>
<string name="pref_advanced_refresh_after_switch">Refresh status after switching</string>
<string name="pref_advanced_refresh_after_switch_desc">If crash after switch, Please trying disable the function</string>
<string name="pref_advanced_disable_safeguard_removable_esim">Allow Disabling / Deleting Active Profile</string> <string name="pref_advanced_disable_safeguard_removable_esim">Allow Disabling / Deleting Active Profile</string>
<string name="pref_advanced_disable_safeguard_removable_esim_desc">By default, this app prevents you from disabling the active profile on a removable eSIM inserted in the device, because doing so may <i>sometimes</i> render it inaccessible.\nCheck this box to <i>remove</i> this safeguard.</string> <string name="pref_advanced_disable_safeguard_removable_esim_desc">By default, this app prevents you from disabling the active profile on a removable eSIM inserted in the device, because doing so may <i>sometimes</i> render it inaccessible.\nCheck this box to <i>remove</i> this safeguard.</string>
<string name="pref_advanced_verbose_logging">Verbose Logging</string> <string name="pref_advanced_verbose_logging">Verbose Logging</string>

View file

@ -60,12 +60,14 @@
<CheckBoxPreference <CheckBoxPreference
app:iconSpaceReserved="false" app:iconSpaceReserved="false"
app:key="pref_developer_unfiltered_profile_list" app:key="pref_developer_unfiltered_profile_list"
app:order="2"
app:summary="@string/pref_developer_unfiltered_profile_list_desc" app:summary="@string/pref_developer_unfiltered_profile_list_desc"
app:title="@string/pref_developer_unfiltered_profile_list" /> app:title="@string/pref_developer_unfiltered_profile_list" />
<CheckBoxPreference <CheckBoxPreference
app:iconSpaceReserved="false" app:iconSpaceReserved="false"
app:key="pref_developer_ignore_tls_certificate" app:key="pref_developer_ignore_tls_certificate"
app:order="3"
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" />

View file

@ -6,9 +6,11 @@ import android.content.pm.PackageManager
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.widget.Toast import android.widget.Toast
import androidx.preference.CheckBoxPreference
import androidx.preference.Preference import androidx.preference.Preference
import im.angry.easyeuicc.R import im.angry.easyeuicc.R
import im.angry.openeuicc.util.encodeHex import im.angry.openeuicc.util.encodeHex
import im.angry.openeuicc.util.preferenceRepository
import java.security.MessageDigest import java.security.MessageDigest
class UnprivilegedSettingsFragment : SettingsFragment() { class UnprivilegedSettingsFragment : SettingsFragment() {
@ -29,8 +31,12 @@ class UnprivilegedSettingsFragment : SettingsFragment() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
super.onCreatePreferences(savedInstanceState, rootKey) super.onCreatePreferences(savedInstanceState, rootKey)
addPreferencesFromResource(R.xml.pref_unprivileged_settings) addPreferencesFromResource(R.xml.pref_unprivileged_settings)
mergePreferenceOverlay("pref_developer_overlay", "pref_developer")
mergePreferenceOverlay("pref_info_overlay", "pref_info") mergePreferenceOverlay("pref_info_overlay", "pref_info")
requirePreference<CheckBoxPreference>("pref_developer_refreshed_after_switch")
.bindBooleanFlow(preferenceRepository.refreshAfterSwitchFlow)
requirePreference<Preference>("pref_info_ara_m").apply { requirePreference<Preference>("pref_info_ara_m").apply {
summary = firstSigner.encodeHex() summary = firstSigner.encodeHex()
setOnPreferenceClickListener { setOnPreferenceClickListener {

View file

@ -1,5 +1,15 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto"> <PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory
app:isPreferenceVisible="false"
app:key="pref_developer_overlay">
<CheckBoxPreference
app:iconSpaceReserved="false"
app:key="pref_developer_refreshed_after_switch"
app:order="1"
app:summary="@string/pref_advanced_refresh_after_switch_desc"
app:title="@string/pref_advanced_refresh_after_switch" />
</PreferenceCategory>
<PreferenceCategory <PreferenceCategory
app:isPreferenceVisible="false" app:isPreferenceVisible="false"
app:key="pref_info_overlay"> app:key="pref_info_overlay">