From 47d5c3881c23548ea96482fa1d8286c30a9988e6 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sat, 16 Nov 2024 18:34:45 -0500 Subject: [PATCH 01/12] ui: Add skeleton of an experimental new download flow This doesn't work yet at all, and is hidden behind an experimental settings switch. --- app-common/src/main/AndroidManifest.xml | 4 ++ .../openeuicc/ui/EuiccManagementFragment.kt | 12 ++++- .../im/angry/openeuicc/ui/SettingsFragment.kt | 6 +-- .../ui/wizard/DownloadWizardActivity.kt | 43 ++++++++++++++++++ .../angry/openeuicc/util/PreferenceUtils.kt | 4 ++ .../src/main/res/drawable/ic_chevron_left.xml | 5 +++ .../main/res/drawable/ic_chevron_right.xml | 5 +++ .../res/layout/activity_download_wizard.xml | 44 +++++++++++++++++++ app-common/src/main/res/values/strings.xml | 6 +++ app-common/src/main/res/xml/pref_settings.xml | 6 +++ 10 files changed, 130 insertions(+), 5 deletions(-) create mode 100644 app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt create mode 100644 app-common/src/main/res/drawable/ic_chevron_left.xml create mode 100644 app-common/src/main/res/drawable/ic_chevron_right.xml create mode 100644 app-common/src/main/res/layout/activity_download_wizard.xml diff --git a/app-common/src/main/AndroidManifest.xml b/app-common/src/main/AndroidManifest.xml index 11f16a6..a33838f 100644 --- a/app-common/src/main/AndroidManifest.xml +++ b/app-common/src/main/AndroidManifest.xml @@ -32,6 +32,10 @@ android:name="im.angry.openeuicc.ui.LogsActivity" android:label="@string/pref_advanced_logs" /> + + ("pref_advanced_verbose_logging") ?.bindBooleanFlow(preferenceRepository.verboseLoggingFlow, PreferenceKeys.VERBOSE_LOGGING) + + findPreference("pref_advanced_experimental_download_wizard") + ?.bindBooleanFlow(preferenceRepository.experimentalDownloadWizardFlow, PreferenceKeys.EXPERIMENTAL_DOWNLOAD_WIZARD) } override fun onStart() { diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt new file mode 100644 index 0000000..4db4b38 --- /dev/null +++ b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt @@ -0,0 +1,43 @@ +package im.angry.openeuicc.ui.wizard + +import android.os.Bundle +import android.view.View +import androidx.activity.OnBackPressedCallback +import androidx.activity.enableEdgeToEdge +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.updatePadding +import im.angry.openeuicc.common.R +import im.angry.openeuicc.ui.BaseEuiccAccessActivity + +class DownloadWizardActivity: BaseEuiccAccessActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + enableEdgeToEdge() + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_download_wizard) + onBackPressedDispatcher.addCallback(object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + // TODO: Actually implement this + } + }) + + val navigation = requireViewById(R.id.download_wizard_navigation) + val origHeight = navigation.layoutParams.height + + ViewCompat.setOnApplyWindowInsetsListener(navigation) { v, insets -> + val bars = insets.getInsets( + WindowInsetsCompat.Type.systemBars() + or WindowInsetsCompat.Type.displayCutout() + ) + v.updatePadding(bars.left, 0, bars.right, bars.bottom) + val newParams = navigation.layoutParams + newParams.height = origHeight + bars.bottom + navigation.layoutParams = newParams + WindowInsetsCompat.CONSUMED + } + } + + override fun onInit() { + + } +} \ No newline at end of file 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 262482a..ebc32d9 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 @@ -25,6 +25,7 @@ object PreferenceKeys { val NOTIFICATION_SWITCH = booleanPreferencesKey("notification_switch") val DISABLE_SAFEGUARD_REMOVABLE_ESIM = booleanPreferencesKey("disable_safeguard_removable_esim") val VERBOSE_LOGGING = booleanPreferencesKey("verbose_logging") + val EXPERIMENTAL_DOWNLOAD_WIZARD = booleanPreferencesKey("experimental_download_wizard") } class PreferenceRepository(context: Context) { @@ -48,6 +49,9 @@ class PreferenceRepository(context: Context) { val verboseLoggingFlow: Flow = dataStore.data.map { it[PreferenceKeys.VERBOSE_LOGGING] ?: false } + val experimentalDownloadWizardFlow: Flow = + dataStore.data.map { it[PreferenceKeys.EXPERIMENTAL_DOWNLOAD_WIZARD] ?: false } + suspend fun updatePreference(key: Preferences.Key, value: T) { dataStore.edit { it[key] = value diff --git a/app-common/src/main/res/drawable/ic_chevron_left.xml b/app-common/src/main/res/drawable/ic_chevron_left.xml new file mode 100644 index 0000000..1152da9 --- /dev/null +++ b/app-common/src/main/res/drawable/ic_chevron_left.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app-common/src/main/res/drawable/ic_chevron_right.xml b/app-common/src/main/res/drawable/ic_chevron_right.xml new file mode 100644 index 0000000..1db5e68 --- /dev/null +++ b/app-common/src/main/res/drawable/ic_chevron_right.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app-common/src/main/res/layout/activity_download_wizard.xml b/app-common/src/main/res/layout/activity_download_wizard.xml new file mode 100644 index 0000000..7f5637c --- /dev/null +++ b/app-common/src/main/res/layout/activity_download_wizard.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app-common/src/main/res/values/strings.xml b/app-common/src/main/res/values/strings.xml index 05ea4c5..47dd897 100644 --- a/app-common/src/main/res/values/strings.xml +++ b/app-common/src/main/res/values/strings.xml @@ -58,6 +58,10 @@ This download may fail This download may fail due to low remaining capacity. + Download Wizard + Back + Next + New nickname Are you sure you want to delete the profile %s? This operation is irreversible. @@ -113,6 +117,8 @@ Enable verbose logs, which may contain sensitive information. Only share your logs with someone you trust after turning this on. Logs View recent debug logs of the application + Experimental Download Wizard + Enable the experimental new download wizard. Note that it is not fully working yet. 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 53395ed..9630612 100644 --- a/app-common/src/main/res/xml/pref_settings.xml +++ b/app-common/src/main/res/xml/pref_settings.xml @@ -41,6 +41,12 @@ app:iconSpaceReserved="false" app:title="@string/pref_advanced_logs" app:summary="@string/pref_advanced_logs_desc" /> + + Date: Sat, 16 Nov 2024 20:37:43 -0500 Subject: [PATCH 02/12] ui: Set up progress bar for the new wizard --- .../ui/wizard/DownloadWizardActivity.kt | 7 ++++++- .../res/layout/activity_download_wizard.xml | 21 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt index 4db4b38..edd9f2e 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt @@ -2,6 +2,7 @@ package im.angry.openeuicc.ui.wizard import android.os.Bundle import android.view.View +import android.widget.ProgressBar import androidx.activity.OnBackPressedCallback import androidx.activity.enableEdgeToEdge import androidx.core.view.ViewCompat @@ -11,6 +12,8 @@ import im.angry.openeuicc.common.R import im.angry.openeuicc.ui.BaseEuiccAccessActivity class DownloadWizardActivity: BaseEuiccAccessActivity() { + private lateinit var progressBar: ProgressBar + override fun onCreate(savedInstanceState: Bundle?) { enableEdgeToEdge() super.onCreate(savedInstanceState) @@ -21,6 +24,8 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() { } }) + progressBar = requireViewById(R.id.progress) + val navigation = requireViewById(R.id.download_wizard_navigation) val origHeight = navigation.layoutParams.height @@ -38,6 +43,6 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() { } override fun onInit() { - + progressBar.visibility = View.GONE } } \ No newline at end of file diff --git a/app-common/src/main/res/layout/activity_download_wizard.xml b/app-common/src/main/res/layout/activity_download_wizard.xml index 7f5637c..605eca2 100644 --- a/app-common/src/main/res/layout/activity_download_wizard.xml +++ b/app-common/src/main/res/layout/activity_download_wizard.xml @@ -4,6 +4,27 @@ android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto"> + + + + Date: Sat, 16 Nov 2024 20:59:36 -0500 Subject: [PATCH 03/12] ui: Hide developer settings behind 7 clicks --- .../im/angry/openeuicc/ui/SettingsFragment.kt | 67 ++++++++++++++++++- .../angry/openeuicc/util/PreferenceUtils.kt | 5 ++ app-common/src/main/res/values/strings.xml | 8 ++- app-common/src/main/res/xml/pref_settings.xml | 14 +++- 4 files changed, 87 insertions(+), 7 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 1981db5..f368732 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 @@ -3,23 +3,48 @@ package im.angry.openeuicc.ui import android.content.Intent import android.net.Uri import android.os.Bundle +import android.widget.Toast import androidx.datastore.preferences.core.Preferences import androidx.lifecycle.lifecycleScope import androidx.preference.CheckBoxPreference import androidx.preference.Preference +import androidx.preference.PreferenceCategory import androidx.preference.PreferenceFragmentCompat import im.angry.openeuicc.common.R import im.angry.openeuicc.util.* import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking class SettingsFragment: PreferenceFragmentCompat() { + private lateinit var developerPref: PreferenceCategory + + // Hidden developer options switch + private var numClicks = 0 + private var lastClickTimestamp = -1L + private var lastToast: Toast? = null + 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 { + preferenceRepository.developerOptionsEnabledFlow.onEach { + developerPref.isVisible = it + }.collect() + } + findPreference("pref_info_app_version") - ?.summary = requireContext().selfAppVersion + ?.apply { + summary = requireContext().selfAppVersion + + // Enable developer options when this is clicked for 7 times + setOnPreferenceClickListener(this@SettingsFragment::onAppVersionClicked) + } findPreference("pref_info_source_code") ?.setOnPreferenceClickListener { @@ -48,7 +73,7 @@ class SettingsFragment: PreferenceFragmentCompat() { findPreference("pref_advanced_verbose_logging") ?.bindBooleanFlow(preferenceRepository.verboseLoggingFlow, PreferenceKeys.VERBOSE_LOGGING) - findPreference("pref_advanced_experimental_download_wizard") + findPreference("pref_developer_experimental_download_wizard") ?.bindBooleanFlow(preferenceRepository.experimentalDownloadWizardFlow, PreferenceKeys.EXPERIMENTAL_DOWNLOAD_WIZARD) } @@ -57,6 +82,44 @@ class SettingsFragment: PreferenceFragmentCompat() { setupRootViewInsets(requireView().requireViewById(androidx.preference.R.id.recycler_view)) } + @Suppress("UNUSED_PARAMETER") + private fun onAppVersionClicked(pref: Preference): Boolean { + if (developerPref.isVisible) return false + val now = System.currentTimeMillis() + if (now - lastClickTimestamp >= 1000) { + numClicks = 1 + } else { + numClicks++ + } + lastClickTimestamp = now + + if (numClicks == 7) { + lifecycleScope.launch { + preferenceRepository.updatePreference( + PreferenceKeys.DEVELOPER_OPTIONS_ENABLED, + true + ) + + lastToast?.cancel() + Toast.makeText( + requireContext(), + R.string.developer_options_enabled, + Toast.LENGTH_SHORT + ).show() + } + } else if (numClicks > 1) { + lastToast?.cancel() + lastToast = Toast.makeText( + requireContext(), + getString(R.string.developer_options_steps, 7 - numClicks), + Toast.LENGTH_SHORT + ) + lastToast!!.show() + } + + return true + } + private fun CheckBoxPreference.bindBooleanFlow(flow: Flow, key: Preferences.Key) { lifecycleScope.launch { flow.collect { isChecked = it } 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 ebc32d9..700c4cd 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 @@ -25,6 +25,7 @@ object PreferenceKeys { val NOTIFICATION_SWITCH = booleanPreferencesKey("notification_switch") val DISABLE_SAFEGUARD_REMOVABLE_ESIM = booleanPreferencesKey("disable_safeguard_removable_esim") val VERBOSE_LOGGING = booleanPreferencesKey("verbose_logging") + val DEVELOPER_OPTIONS_ENABLED = booleanPreferencesKey("developer_options_enabled") val EXPERIMENTAL_DOWNLOAD_WIZARD = booleanPreferencesKey("experimental_download_wizard") } @@ -49,6 +50,10 @@ class PreferenceRepository(context: Context) { val verboseLoggingFlow: Flow = dataStore.data.map { it[PreferenceKeys.VERBOSE_LOGGING] ?: false } + // ---- Developer Options ---- + val developerOptionsEnabledFlow: Flow = + dataStore.data.map { it[PreferenceKeys.DEVELOPER_OPTIONS_ENABLED] ?: false } + val experimentalDownloadWizardFlow: Flow = dataStore.data.map { it[PreferenceKeys.EXPERIMENTAL_DOWNLOAD_WIZARD] ?: false } diff --git a/app-common/src/main/res/values/strings.xml b/app-common/src/main/res/values/strings.xml index 47dd897..062a5c0 100644 --- a/app-common/src/main/res/values/strings.xml +++ b/app-common/src/main/res/values/strings.xml @@ -101,6 +101,9 @@ Save Logs at %s + You are %d steps away from being a developer. + You are now a developer! + Settings Notifications eSIM profile operations send notifications to the carrier. Fine-tune this behavior as needed here. @@ -117,8 +120,9 @@ Enable verbose logs, which may contain sensitive information. Only share your logs with someone you trust after turning this on. Logs View recent debug logs of the application - Experimental Download Wizard - Enable the experimental new download wizard. Note that it is not fully working yet. + Developer Options + Experimental Download Wizard + Enable the experimental new download wizard. Note that it is not fully working yet. 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 9630612..150aca5 100644 --- a/app-common/src/main/res/xml/pref_settings.xml +++ b/app-common/src/main/res/xml/pref_settings.xml @@ -42,11 +42,19 @@ app:title="@string/pref_advanced_logs" app:summary="@string/pref_advanced_logs_desc" /> + + + + + app:title="@string/pref_developer_experimental_download_wizard" + app:summary="@string/pref_developer_experimental_download_wizard_desc" /> + Date: Sun, 17 Nov 2024 03:43:38 +0100 Subject: [PATCH 04/12] feat: ignore tls certificate (#66) Reviewed-on: https://gitea.angry.im/PeterCxy/OpenEUICC/pulls/66 Co-authored-by: septs Co-committed-by: septs --- .../core/DefaultEuiccChannelFactory.kt | 6 +++-- .../angry/openeuicc/core/EuiccChannelImpl.kt | 5 ++-- .../im/angry/openeuicc/ui/SettingsFragment.kt | 3 +++ .../angry/openeuicc/util/PreferenceUtils.kt | 9 ++++++++ app-common/src/main/res/values/strings.xml | 2 ++ app-common/src/main/res/xml/pref_settings.xml | 6 +++++ .../core/PrivilegedEuiccChannelFactory.kt | 3 ++- .../lpac_jni/impl/HttpInterfaceImpl.kt | 23 +++++++++++++++---- .../lpac_jni/impl/IgnoreTLSCertificate.kt | 22 ++++++++++++++++++ 9 files changed, 69 insertions(+), 10 deletions(-) create mode 100644 libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/IgnoreTLSCertificate.kt 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 410cccc..a8fa1d5 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 @@ -42,7 +42,8 @@ open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccCha port, context.preferenceRepository.verboseLoggingFlow ), - context.preferenceRepository.verboseLoggingFlow + context.preferenceRepository.verboseLoggingFlow, + context.preferenceRepository.ignoreTLSCertificateFlow, ).also { Log.i(DefaultEuiccChannelManager.TAG, "Is OMAPI channel, setting MSS to 60") it.lpa.setEs10xMss(60) @@ -72,7 +73,8 @@ open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccCha bulkOut, context.preferenceRepository.verboseLoggingFlow ), - context.preferenceRepository.verboseLoggingFlow + context.preferenceRepository.verboseLoggingFlow, + context.preferenceRepository.ignoreTLSCertificateFlow, ) } diff --git a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelImpl.kt b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelImpl.kt index 9bccbff..79dec34 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelImpl.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelImpl.kt @@ -11,14 +11,15 @@ class EuiccChannelImpl( override val type: String, override val port: UiccPortInfoCompat, apduInterface: ApduInterface, - verboseLoggingFlow: Flow + verboseLoggingFlow: Flow, + ignoreTLSCertificateFlow: Flow ) : EuiccChannel { override val slotId = port.card.physicalSlotIndex override val logicalSlotId = port.logicalSlotIndex override val portId = port.portIndex override val lpa: LocalProfileAssistant = - LocalProfileAssistantImpl(apduInterface, HttpInterfaceImpl(verboseLoggingFlow)) + LocalProfileAssistantImpl(apduInterface, HttpInterfaceImpl(verboseLoggingFlow, ignoreTLSCertificateFlow)) override val valid: Boolean get() = lpa.valid 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 f368732..89963cb 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 @@ -75,6 +75,9 @@ class SettingsFragment: PreferenceFragmentCompat() { findPreference("pref_developer_experimental_download_wizard") ?.bindBooleanFlow(preferenceRepository.experimentalDownloadWizardFlow, PreferenceKeys.EXPERIMENTAL_DOWNLOAD_WIZARD) + + findPreference("pref_ignore_tls_certificate") + ?.bindBooleanFlow(preferenceRepository.ignoreTLSCertificateFlow, PreferenceKeys.IGNORE_TLS_CERTIFICATE) } override fun onStart() { 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 700c4cd..505630e 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 @@ -20,13 +20,19 @@ val Fragment.preferenceRepository: PreferenceRepository get() = requireContext().preferenceRepository object PreferenceKeys { + // ---- Profile Notifications ---- val NOTIFICATION_DOWNLOAD = booleanPreferencesKey("notification_download") val NOTIFICATION_DELETE = booleanPreferencesKey("notification_delete") val NOTIFICATION_SWITCH = booleanPreferencesKey("notification_switch") + + // ---- Advanced ---- val DISABLE_SAFEGUARD_REMOVABLE_ESIM = booleanPreferencesKey("disable_safeguard_removable_esim") val VERBOSE_LOGGING = booleanPreferencesKey("verbose_logging") + + // ---- Developer Options ---- val DEVELOPER_OPTIONS_ENABLED = booleanPreferencesKey("developer_options_enabled") val EXPERIMENTAL_DOWNLOAD_WIZARD = booleanPreferencesKey("experimental_download_wizard") + val IGNORE_TLS_CERTIFICATE = booleanPreferencesKey("ignore_tls_certificate") } class PreferenceRepository(context: Context) { @@ -57,6 +63,9 @@ class PreferenceRepository(context: Context) { val experimentalDownloadWizardFlow: Flow = dataStore.data.map { it[PreferenceKeys.EXPERIMENTAL_DOWNLOAD_WIZARD] ?: false } + val ignoreTLSCertificateFlow: Flow = + dataStore.data.map { it[PreferenceKeys.IGNORE_TLS_CERTIFICATE] ?: false } + suspend fun updatePreference(key: Preferences.Key, value: T) { dataStore.edit { it[key] = value diff --git a/app-common/src/main/res/values/strings.xml b/app-common/src/main/res/values/strings.xml index 062a5c0..bdb89ca 100644 --- a/app-common/src/main/res/values/strings.xml +++ b/app-common/src/main/res/values/strings.xml @@ -123,6 +123,8 @@ Developer Options Experimental Download Wizard Enable the experimental new download wizard. Note that it is not fully working yet. + Ignore SM-DP+ TLS certificate + Ignore SM-DP+ TLS certificate, allow any RSP 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 150aca5..d43c84b 100644 --- a/app-common/src/main/res/xml/pref_settings.xml +++ b/app-common/src/main/res/xml/pref_settings.xml @@ -55,6 +55,12 @@ app:title="@string/pref_developer_experimental_download_wizard" app:summary="@string/pref_developer_experimental_download_wizard_desc" /> + + ) : HttpInterface { +class HttpInterfaceImpl( + private val verboseLoggingFlow: Flow, + private val ignoreTLSCertificateFlow: Flow +) : HttpInterface { companion object { private const val TAG = "HttpInterfaceImpl" } @@ -36,9 +40,6 @@ class HttpInterfaceImpl(private val verboseLoggingFlow: Flow) : HttpInt } try { - val sslContext = SSLContext.getInstance("TLS") - sslContext.init(null, trustManagers, SecureRandom()) - val conn = parsedUrl.openConnection() as HttpsURLConnection conn.connectTimeout = 2000 @@ -47,7 +48,7 @@ class HttpInterfaceImpl(private val verboseLoggingFlow: Flow) : HttpInt conn.readTimeout = 1000 } - conn.sslSocketFactory = sslContext.socketFactory + conn.sslSocketFactory = getSocketFactory() conn.requestMethod = "POST" conn.doInput = true conn.doOutput = true @@ -79,6 +80,18 @@ class HttpInterfaceImpl(private val verboseLoggingFlow: Flow) : HttpInt } } + private fun getSocketFactory(): SSLSocketFactory { + val trustManagers = + if (runBlocking { ignoreTLSCertificateFlow.first() }) { + arrayOf(IgnoreTLSCertificate()) + } else { + this.trustManagers + } + val sslContext = SSLContext.getInstance("TLS") + sslContext.init(null, trustManagers, SecureRandom()) + return sslContext.socketFactory + } + override fun usePublicKeyIds(pkids: Array) { val trustManagerFactory = TrustManagerFactory.getInstance("PKIX").apply { init(keyIdToKeystore(pkids)) diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/IgnoreTLSCertificate.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/IgnoreTLSCertificate.kt new file mode 100644 index 0000000..7b13282 --- /dev/null +++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/IgnoreTLSCertificate.kt @@ -0,0 +1,22 @@ +package net.typeblog.lpac_jni.impl + +import android.annotation.SuppressLint +import java.security.cert.X509Certificate +import javax.net.ssl.X509TrustManager + +@SuppressLint("CustomX509TrustManager") +class IgnoreTLSCertificate : X509TrustManager { + @SuppressLint("TrustAllX509TrustManager") + override fun checkClientTrusted(p0: Array?, p1: String?) { + return + } + + @SuppressLint("TrustAllX509TrustManager") + override fun checkServerTrusted(p0: Array?, p1: String?) { + return + } + + override fun getAcceptedIssuers(): Array { + return emptyArray() + } +} \ No newline at end of file From 375d13b7c481021108b94620bed77a250cc951bd Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sun, 17 Nov 2024 17:32:42 -0500 Subject: [PATCH 05/12] ui: Start designing UI for selectiing slot in the new download flow incomplete --- .../ui/wizard/DownloadWizardActivity.kt | 69 +++++++++++ .../DownloadWizardSlotSelectFragment.kt | 108 ++++++++++++++++++ .../util/EuiccChannelFragmentUtils.kt | 6 +- .../res/layout/activity_download_wizard.xml | 9 ++ .../main/res/layout/download_slot_item.xml | 77 +++++++++++++ .../layout/fragment_download_slot_select.xml | 29 +++++ app-common/src/main/res/values/strings.xml | 4 + 7 files changed, 299 insertions(+), 3 deletions(-) create mode 100644 app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt create mode 100644 app-common/src/main/res/layout/download_slot_item.xml create mode 100644 app-common/src/main/res/layout/fragment_download_slot_select.xml diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt index edd9f2e..4a81834 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt @@ -2,17 +2,24 @@ package im.angry.openeuicc.ui.wizard import android.os.Bundle import android.view.View +import android.widget.Button import android.widget.ProgressBar import androidx.activity.OnBackPressedCallback import androidx.activity.enableEdgeToEdge import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.updatePadding +import androidx.fragment.app.Fragment import im.angry.openeuicc.common.R import im.angry.openeuicc.ui.BaseEuiccAccessActivity +import im.angry.openeuicc.util.* class DownloadWizardActivity: BaseEuiccAccessActivity() { private lateinit var progressBar: ProgressBar + private lateinit var nextButton: Button + private lateinit var prevButton: Button + + private var currentFragment: DownloadWizardStepFragment? = null override fun onCreate(savedInstanceState: Bundle?) { enableEdgeToEdge() @@ -25,6 +32,8 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() { }) progressBar = requireViewById(R.id.progress) + nextButton = requireViewById(R.id.download_wizard_next) + prevButton = requireViewById(R.id.download_wizard_back) val navigation = requireViewById(R.id.download_wizard_navigation) val origHeight = navigation.layoutParams.height @@ -40,9 +49,69 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() { navigation.layoutParams = newParams WindowInsetsCompat.CONSUMED } + + val fragmentRoot = requireViewById(R.id.step_fragment_container) + ViewCompat.setOnApplyWindowInsetsListener(fragmentRoot) { v, insets -> + val bars = insets.getInsets( + WindowInsetsCompat.Type.systemBars() + or WindowInsetsCompat.Type.displayCutout() + ) + v.updatePadding(bars.left, bars.top, bars.right, 0) + WindowInsetsCompat.CONSUMED + } } override fun onInit() { progressBar.visibility = View.GONE + showFragment(DownloadWizardSlotSelectFragment()) + } + + private fun showFragment(nextFrag: DownloadWizardStepFragment) { + currentFragment = nextFrag + supportFragmentManager.beginTransaction().replace(R.id.step_fragment_container, nextFrag) + .commit() + refreshButtons() + } + + private fun refreshButtons() { + currentFragment?.let { + nextButton.visibility = if (it.hasNext) { + View.VISIBLE + } else { + View.GONE + } + prevButton.visibility = if (it.hasNext) { + View.VISIBLE + } else { + View.GONE + } + } + } + + abstract class DownloadWizardStepFragment : Fragment(), OpenEuiccContextMarker { + abstract val hasNext: Boolean + abstract val hasPrev: Boolean + abstract fun createNextFragment(): DownloadWizardStepFragment + abstract fun createPrevFragment(): DownloadWizardStepFragment + + protected fun hideProgressBar() { + (requireActivity() as DownloadWizardActivity).progressBar.visibility = View.GONE + } + + protected fun showProgressBar(progressValue: Int) { + (requireActivity() as DownloadWizardActivity).progressBar.apply { + visibility = View.VISIBLE + if (progressValue >= 0) { + isIndeterminate = false + progress = progressValue + } else { + isIndeterminate = true + } + } + } + + protected fun refreshButtons() { + (requireActivity() as DownloadWizardActivity).refreshButtons() + } } } \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt new file mode 100644 index 0000000..6627f1b --- /dev/null +++ b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt @@ -0,0 +1,108 @@ +package im.angry.openeuicc.ui.wizard + +import android.annotation.SuppressLint +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import im.angry.openeuicc.common.R +import im.angry.openeuicc.util.* +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.launch +import net.typeblog.lpac_jni.LocalProfileInfo + +class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardStepFragment() { + private data class SlotInfo( + val logicalSlotId: Int, + val eID: String, + val enabledProfileName: String? + ) + + private var loaded = false + + private val adapter = SlotInfoAdapter() + + override val hasNext: Boolean + get() = loaded + override val hasPrev: Boolean + get() = false + + override fun createNextFragment(): DownloadWizardActivity.DownloadWizardStepFragment { + TODO("Not yet implemented") + } + + override fun createPrevFragment(): DownloadWizardActivity.DownloadWizardStepFragment { + TODO("Not yet implemented") + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + val view = inflater.inflate(R.layout.fragment_download_slot_select, container, false) + val recyclerView = view.requireViewById(R.id.download_slot_list) + recyclerView.adapter = adapter + recyclerView.layoutManager = + LinearLayoutManager(view.context, LinearLayoutManager.VERTICAL, false) + return view + } + + override fun onStart() { + super.onStart() + if (!loaded) { + lifecycleScope.launch { init() } + } + } + + @SuppressLint("NotifyDataSetChanged") + private suspend fun init() { + showProgressBar(-1) + val slots = euiccChannelManager.flowEuiccPorts().map { (slotId, portId) -> + euiccChannelManager.withEuiccChannel(slotId, portId) { channel -> + SlotInfo( + channel.logicalSlotId, + channel.lpa.eID, + channel.lpa.profiles.find { it.state == LocalProfileInfo.State.Enabled }?.displayName + ) + } + }.toList() + adapter.slots = slots + adapter.notifyDataSetChanged() + hideProgressBar() + refreshButtons() + } + + private class SlotItemHolder(val root: View) : ViewHolder(root) { + private val title = root.requireViewById(R.id.slot_item_title) + private val eID = root.requireViewById(R.id.slot_item_eid) + private val activeProfile = root.requireViewById(R.id.slot_item_active_profile) + + fun bind(item: SlotInfo) { + title.text = root.context.getString(R.string.download_wizard_slot_title, item.logicalSlotId) + eID.text = item.eID + activeProfile.text = item.enabledProfileName ?: root.context.getString(R.string.unknown) + } + } + + private class SlotInfoAdapter : RecyclerView.Adapter() { + var slots: List = listOf() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SlotItemHolder { + val root = LayoutInflater.from(parent.context).inflate(R.layout.download_slot_item, parent, false) + return SlotItemHolder(root) + } + + override fun getItemCount(): Int = slots.size + + override fun onBindViewHolder(holder: SlotItemHolder, position: Int) { + holder.bind(slots[position]) + } + } +} \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/util/EuiccChannelFragmentUtils.kt b/app-common/src/main/java/im/angry/openeuicc/util/EuiccChannelFragmentUtils.kt index f0cf193..3f3c4ee 100644 --- a/app-common/src/main/java/im/angry/openeuicc/util/EuiccChannelFragmentUtils.kt +++ b/app-common/src/main/java/im/angry/openeuicc/util/EuiccChannelFragmentUtils.kt @@ -32,9 +32,9 @@ val T.portId: Int where T: Fragment, T: EuiccChannelFragmentMarker val T.isUsb: Boolean where T: Fragment, T: EuiccChannelFragmentMarker get() = requireArguments().getInt("slotId") == EuiccChannelManager.USB_CHANNEL_ID -val T.euiccChannelManager: EuiccChannelManager where T: Fragment, T: EuiccChannelFragmentMarker +val T.euiccChannelManager: EuiccChannelManager where T: Fragment, T: OpenEuiccContextMarker get() = (requireActivity() as BaseEuiccAccessActivity).euiccChannelManager -val T.euiccChannelManagerService: EuiccChannelManagerService where T: Fragment, T: EuiccChannelFragmentMarker +val T.euiccChannelManagerService: EuiccChannelManagerService where T: Fragment, T: OpenEuiccContextMarker get() = (requireActivity() as BaseEuiccAccessActivity).euiccChannelManagerService suspend fun T.withEuiccChannel(fn: suspend (EuiccChannel) -> R): R where T : Fragment, T : EuiccChannelFragmentMarker { @@ -42,7 +42,7 @@ suspend fun T.withEuiccChannel(fn: suspend (EuiccChannel) -> R): R where return euiccChannelManager.withEuiccChannel(slotId, portId, fn) } -suspend fun T.ensureEuiccChannelManager() where T: Fragment, T: EuiccChannelFragmentMarker = +suspend fun T.ensureEuiccChannelManager() where T: Fragment, T: OpenEuiccContextMarker = (requireActivity() as BaseEuiccAccessActivity).euiccChannelManagerLoaded.await() interface EuiccProfilesChangedListener { diff --git a/app-common/src/main/res/layout/activity_download_wizard.xml b/app-common/src/main/res/layout/activity_download_wizard.xml index 605eca2..79513bb 100644 --- a/app-common/src/main/res/layout/activity_download_wizard.xml +++ b/app-common/src/main/res/layout/activity_download_wizard.xml @@ -4,6 +4,15 @@ android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto"> + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app-common/src/main/res/layout/fragment_download_slot_select.xml b/app-common/src/main/res/layout/fragment_download_slot_select.xml new file mode 100644 index 0000000..8d20434 --- /dev/null +++ b/app-common/src/main/res/layout/fragment_download_slot_select.xml @@ -0,0 +1,29 @@ + + + + + + + + \ No newline at end of file diff --git a/app-common/src/main/res/values/strings.xml b/app-common/src/main/res/values/strings.xml index bdb89ca..64aeaed 100644 --- a/app-common/src/main/res/values/strings.xml +++ b/app-common/src/main/res/values/strings.xml @@ -61,6 +61,10 @@ Download Wizard Back Next + Select an eSIM slot: + Logical slot %d + eID: + Active Profile: New nickname From 1087a676d4e5e00d2081dfd195e7f57234a90a36 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sun, 17 Nov 2024 19:04:41 -0500 Subject: [PATCH 06/12] ui: Fix slot item alignment in new wizard --- app-common/src/main/res/layout/download_slot_item.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app-common/src/main/res/layout/download_slot_item.xml b/app-common/src/main/res/layout/download_slot_item.xml index b1a5810..1cf188b 100644 --- a/app-common/src/main/res/layout/download_slot_item.xml +++ b/app-common/src/main/res/layout/download_slot_item.xml @@ -22,6 +22,7 @@ android:id="@+id/slot_item_eid_label" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:minWidth="100dp" android:text="@string/download_wizard_slot_eid" android:textSize="14sp" /> @@ -35,6 +36,7 @@ android:id="@+id/slot_item_active_profile_label" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:minWidth="100dp" android:text="@string/download_wizard_slot_active_profile" android:textSize="14sp" /> From 93e7297caa2484734765efe39a3a31fc47706a30 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sun, 17 Nov 2024 19:14:00 -0500 Subject: [PATCH 07/12] ui: Implement single selection for new wizard --- .../DownloadWizardSlotSelectFragment.kt | 32 ++++++++++++++++--- .../main/res/layout/download_slot_item.xml | 7 ++-- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt index 6627f1b..b124062 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt @@ -5,8 +5,10 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.CheckBox import android.widget.TextView import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.ViewHolder @@ -51,6 +53,7 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt recyclerView.adapter = adapter recyclerView.layoutManager = LinearLayoutManager(view.context, LinearLayoutManager.VERTICAL, false) + recyclerView.addItemDecoration(DividerItemDecoration(requireContext(), LinearLayoutManager.VERTICAL)) return view } @@ -79,30 +82,51 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt refreshButtons() } - private class SlotItemHolder(val root: View) : ViewHolder(root) { + private class SlotItemHolder(val adapter: SlotInfoAdapter, val root: View) : ViewHolder(root) { private val title = root.requireViewById(R.id.slot_item_title) private val eID = root.requireViewById(R.id.slot_item_eid) private val activeProfile = root.requireViewById(R.id.slot_item_active_profile) + private val checkBox = root.requireViewById(R.id.slot_checkbox) - fun bind(item: SlotInfo) { + private var curIdx = -1 + + init { + root.setOnClickListener(this::onSelect) + checkBox.setOnClickListener(this::onSelect) + } + + @Suppress("UNUSED_PARAMETER") + fun onSelect(view: View) { + if (curIdx < 0) return + if (adapter.currentSelectedIdx == curIdx) return + val lastIdx = adapter.currentSelectedIdx + adapter.currentSelectedIdx = curIdx + adapter.notifyItemChanged(lastIdx) + adapter.notifyItemChanged(curIdx) + } + + fun bind(item: SlotInfo, idx: Int) { + curIdx = idx title.text = root.context.getString(R.string.download_wizard_slot_title, item.logicalSlotId) eID.text = item.eID activeProfile.text = item.enabledProfileName ?: root.context.getString(R.string.unknown) + checkBox.isChecked = adapter.currentSelectedIdx == idx } } private class SlotInfoAdapter : RecyclerView.Adapter() { var slots: List = listOf() + var currentSelectedIdx = 0 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SlotItemHolder { val root = LayoutInflater.from(parent.context).inflate(R.layout.download_slot_item, parent, false) - return SlotItemHolder(root) + return SlotItemHolder(this, root) } override fun getItemCount(): Int = slots.size override fun onBindViewHolder(holder: SlotItemHolder, position: Int) { - holder.bind(slots[position]) + holder.bind(slots[position], position) } } } \ No newline at end of file diff --git a/app-common/src/main/res/layout/download_slot_item.xml b/app-common/src/main/res/layout/download_slot_item.xml index 1cf188b..45a7cc9 100644 --- a/app-common/src/main/res/layout/download_slot_item.xml +++ b/app-common/src/main/res/layout/download_slot_item.xml @@ -3,10 +3,11 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="10sp" - android:layout_marginTop="10sp" + android:paddingBottom="20sp" + android:paddingTop="10sp" android:paddingStart="20sp" - android:paddingEnd="20sp"> + android:paddingEnd="20sp" + android:background="?attr/selectableItemBackground"> Date: Sun, 17 Nov 2024 19:15:42 -0500 Subject: [PATCH 08/12] ui: Fixup loaded state --- .../java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt | 2 +- .../openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt index 4a81834..2958a7f 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt @@ -80,7 +80,7 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() { } else { View.GONE } - prevButton.visibility = if (it.hasNext) { + prevButton.visibility = if (it.hasPrev) { View.VISIBLE } else { View.GONE diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt index b124062..cbbf792 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt @@ -79,6 +79,7 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt adapter.slots = slots adapter.notifyDataSetChanged() hideProgressBar() + loaded = true refreshButtons() } From 4fb59a4b015ba3251eeb5ac3a51c701b044ee1ad Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sun, 17 Nov 2024 19:17:29 -0500 Subject: [PATCH 09/12] ui: Fixup strings for eSIM slot selection --- app-common/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app-common/src/main/res/values/strings.xml b/app-common/src/main/res/values/strings.xml index 64aeaed..dad1218 100644 --- a/app-common/src/main/res/values/strings.xml +++ b/app-common/src/main/res/values/strings.xml @@ -61,7 +61,7 @@ Download Wizard Back Next - Select an eSIM slot: + Confirm the eSIM slot: Logical slot %d eID: Active Profile: From 87f36f4166cb62c52bf11f312494d5169ebe3228 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sun, 17 Nov 2024 21:41:40 -0500 Subject: [PATCH 10/12] ui: Expose more info in slot select fragment --- .../DownloadWizardSlotSelectFragment.kt | 19 +++++++++++++++++++ .../main/res/layout/download_slot_item.xml | 16 +++++++++++++++- .../layout/fragment_download_slot_select.xml | 3 +-- app-common/src/main/res/values/strings.xml | 4 ++++ 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt index cbbf792..253fbdf 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt @@ -22,6 +22,9 @@ import net.typeblog.lpac_jni.LocalProfileInfo class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardStepFragment() { private data class SlotInfo( val logicalSlotId: Int, + val isRemovable: Boolean, + val hasMultiplePorts: Boolean, + val portId: Int, val eID: String, val enabledProfileName: String? ) @@ -71,6 +74,9 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt euiccChannelManager.withEuiccChannel(slotId, portId) { channel -> SlotInfo( channel.logicalSlotId, + channel.port.card.isRemovable, + channel.port.card.ports.size > 1, + channel.portId, channel.lpa.eID, channel.lpa.profiles.find { it.state == LocalProfileInfo.State.Enabled }?.displayName ) @@ -85,6 +91,7 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt private class SlotItemHolder(val adapter: SlotInfoAdapter, val root: View) : ViewHolder(root) { private val title = root.requireViewById(R.id.slot_item_title) + private val type = root.requireViewById(R.id.slot_item_type) private val eID = root.requireViewById(R.id.slot_item_eid) private val activeProfile = root.requireViewById(R.id.slot_item_active_profile) private val checkBox = root.requireViewById(R.id.slot_checkbox) @@ -108,6 +115,18 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt fun bind(item: SlotInfo, idx: Int) { curIdx = idx + + type.text = if (item.isRemovable) { + root.context.getString(R.string.download_wizard_slot_type_removable) + } else if (!item.hasMultiplePorts) { + root.context.getString(R.string.download_wizard_slot_type_internal) + } else { + root.context.getString( + R.string.download_wizard_slot_type_internal_port, + item.portId + ) + } + title.text = root.context.getString(R.string.download_wizard_slot_title, item.logicalSlotId) eID.text = item.eID activeProfile.text = item.enabledProfileName ?: root.context.getString(R.string.unknown) diff --git a/app-common/src/main/res/layout/download_slot_item.xml b/app-common/src/main/res/layout/download_slot_item.xml index 45a7cc9..fa06b4c 100644 --- a/app-common/src/main/res/layout/download_slot_item.xml +++ b/app-common/src/main/res/layout/download_slot_item.xml @@ -19,6 +19,20 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + + + + app:layout_constrainedHeight="true" /> \ No newline at end of file diff --git a/app-common/src/main/res/values/strings.xml b/app-common/src/main/res/values/strings.xml index dad1218..609fe73 100644 --- a/app-common/src/main/res/values/strings.xml +++ b/app-common/src/main/res/values/strings.xml @@ -63,6 +63,10 @@ Next Confirm the eSIM slot: Logical slot %d + Type: + Removable + Internal + Internal, port %d eID: Active Profile: From 6458f54db2f4e44d36f11f379e6c2544d0319b3b Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sun, 17 Nov 2024 21:42:41 -0500 Subject: [PATCH 11/12] ui: Ensure EuiccChannelManager is available in slot select fragment --- .../openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt index 253fbdf..3617b23 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt @@ -69,6 +69,7 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt @SuppressLint("NotifyDataSetChanged") private suspend fun init() { + ensureEuiccChannelManager() showProgressBar(-1) val slots = euiccChannelManager.flowEuiccPorts().map { (slotId, portId) -> euiccChannelManager.withEuiccChannel(slotId, portId) { channel -> From c4b513fc0affa3ef0a3a1d3ad6adafa009590608 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sun, 17 Nov 2024 22:00:03 -0500 Subject: [PATCH 12/12] ui: Save selected slot as state in DownloadWizardActivity --- .../openeuicc/ui/EuiccManagementFragment.kt | 5 ++- .../ui/wizard/DownloadWizardActivity.kt | 17 ++++++++-- .../DownloadWizardSlotSelectFragment.kt | 32 +++++++++++++------ 3 files changed, 41 insertions(+), 13 deletions(-) diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt index c3ecbf3..6ab6935 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt @@ -109,7 +109,10 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener, fab.setOnClickListener { lifecycleScope.launch { if (preferenceRepository.experimentalDownloadWizardFlow.first()) { - startActivity(Intent(requireContext(), DownloadWizardActivity::class.java)) + Intent(requireContext(), DownloadWizardActivity::class.java).apply { + putExtra("selectedLogicalSlot", logicalSlotId) + startActivity(this) + } } else { ProfileDownloadFragment.newInstance(slotId, portId) .show(childFragmentManager, ProfileDownloadFragment.TAG) diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt index 2958a7f..408c55d 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt @@ -15,6 +15,12 @@ import im.angry.openeuicc.ui.BaseEuiccAccessActivity import im.angry.openeuicc.util.* class DownloadWizardActivity: BaseEuiccAccessActivity() { + data class DownloadWizardState( + var selectedLogicalSlot: Int + ) + + private lateinit var state: DownloadWizardState + private lateinit var progressBar: ProgressBar private lateinit var nextButton: Button private lateinit var prevButton: Button @@ -31,6 +37,10 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() { } }) + state = DownloadWizardState( + intent.getIntExtra("selectedLogicalSlot", 0) + ) + progressBar = requireViewById(R.id.progress) nextButton = requireViewById(R.id.download_wizard_next) prevButton = requireViewById(R.id.download_wizard_back) @@ -89,10 +99,13 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() { } abstract class DownloadWizardStepFragment : Fragment(), OpenEuiccContextMarker { + protected val state: DownloadWizardState + get() = (requireActivity() as DownloadWizardActivity).state + abstract val hasNext: Boolean abstract val hasPrev: Boolean - abstract fun createNextFragment(): DownloadWizardStepFragment - abstract fun createPrevFragment(): DownloadWizardStepFragment + abstract fun createNextFragment(): DownloadWizardStepFragment? + abstract fun createPrevFragment(): DownloadWizardStepFragment? protected fun hideProgressBar() { (requireActivity() as DownloadWizardActivity).progressBar.visibility = View.GONE diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt index 3617b23..6720242 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt @@ -34,17 +34,15 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt private val adapter = SlotInfoAdapter() override val hasNext: Boolean - get() = loaded + get() = loaded && adapter.slots.isNotEmpty() override val hasPrev: Boolean - get() = false + get() = true - override fun createNextFragment(): DownloadWizardActivity.DownloadWizardStepFragment { + override fun createNextFragment(): DownloadWizardActivity.DownloadWizardStepFragment? { TODO("Not yet implemented") } - override fun createPrevFragment(): DownloadWizardActivity.DownloadWizardStepFragment { - TODO("Not yet implemented") - } + override fun createPrevFragment(): DownloadWizardActivity.DownloadWizardStepFragment? = null override fun onCreateView( inflater: LayoutInflater, @@ -84,13 +82,25 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt } }.toList() adapter.slots = slots + + // Ensure we always have a selected slot by default + val selectedIdx = slots.indexOfFirst { it.logicalSlotId == state.selectedLogicalSlot } + adapter.currentSelectedIdx = if (selectedIdx > 0) { + selectedIdx + } else { + if (slots.isNotEmpty()) { + state.selectedLogicalSlot = slots[0].logicalSlotId + } + 0 + } + adapter.notifyDataSetChanged() hideProgressBar() loaded = true refreshButtons() } - private class SlotItemHolder(val adapter: SlotInfoAdapter, val root: View) : ViewHolder(root) { + private inner class SlotItemHolder(val root: View) : ViewHolder(root) { private val title = root.requireViewById(R.id.slot_item_title) private val type = root.requireViewById(R.id.slot_item_type) private val eID = root.requireViewById(R.id.slot_item_eid) @@ -112,6 +122,8 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt adapter.currentSelectedIdx = curIdx adapter.notifyItemChanged(lastIdx) adapter.notifyItemChanged(curIdx) + // Selected index isn't logical slot ID directly, needs a conversion + state.selectedLogicalSlot = adapter.slots[adapter.currentSelectedIdx].logicalSlotId } fun bind(item: SlotInfo, idx: Int) { @@ -135,13 +147,13 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt } } - private class SlotInfoAdapter : RecyclerView.Adapter() { + private inner class SlotInfoAdapter : RecyclerView.Adapter() { var slots: List = listOf() - var currentSelectedIdx = 0 + var currentSelectedIdx = -1 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SlotItemHolder { val root = LayoutInflater.from(parent.context).inflate(R.layout.download_slot_item, parent, false) - return SlotItemHolder(this, root) + return SlotItemHolder(root) } override fun getItemCount(): Int = slots.size