From adfdafe917289dd9d2deb8c97489d365f001908c Mon Sep 17 00:00:00 2001 From: septs Date: Wed, 5 Mar 2025 23:12:01 +0800 Subject: [PATCH 01/15] feat: add deep link supports --- .../ui/wizard/DownloadWizardActivity.kt | 39 ++++++++++++------- .../DownloadWizardMethodSelectFragment.kt | 5 +++ .../im/angry/openeuicc/util/ActivationCode.kt | 20 ++++++++-- app-unpriv/src/main/AndroidManifest.xml | 18 +++++++++ 4 files changed, 63 insertions(+), 19 deletions(-) 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 e342dee..1f5b97b 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 @@ -19,7 +19,6 @@ import im.angry.openeuicc.ui.BaseEuiccAccessActivity import im.angry.openeuicc.util.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import net.typeblog.lpac_jni.LocalProfileAssistant class DownloadWizardActivity: BaseEuiccAccessActivity() { @@ -61,15 +60,15 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() { }) state = DownloadWizardState( - null, - intent.getIntExtra("selectedLogicalSlot", 0), - "", - null, - null, - null, - false, - -1, - null + currentStepFragmentClassName = null, + selectedLogicalSlot = intent.getIntExtra("selectedLogicalSlot", 0), + smdp = "", + matchingId = null, + confirmationCode = null, + imei = null, + downloadStarted = false, + downloadTaskID = -1, + downloadError = null ) progressBar = requireViewById(R.id.progress) @@ -237,18 +236,28 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() { } } + internal fun getActivationCodeFromIntent(): String? { + val uri = intent.data ?: return null + if (uri.scheme != "openeuicc" || uri.host != "lpa") return null + return uri.path?.drop(1) + } + abstract class DownloadWizardStepFragment : Fragment(), OpenEuiccContextMarker { protected val state: DownloadWizardState - get() = (requireActivity() as DownloadWizardActivity).state + get() = requireWizardActivity().state abstract val hasNext: Boolean abstract val hasPrev: Boolean abstract fun createNextFragment(): DownloadWizardStepFragment? abstract fun createPrevFragment(): DownloadWizardStepFragment? + protected fun requireWizardActivity(): DownloadWizardActivity { + return requireActivity() as DownloadWizardActivity + } + protected fun gotoNextFragment(next: DownloadWizardStepFragment? = null) { val realNext = next ?: createNextFragment() - (requireActivity() as DownloadWizardActivity).showFragment( + requireWizardActivity().showFragment( realNext!!, R.anim.slide_in_right, R.anim.slide_out_left @@ -256,11 +265,11 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() { } protected fun hideProgressBar() { - (requireActivity() as DownloadWizardActivity).progressBar.visibility = View.GONE + requireWizardActivity().progressBar.visibility = View.GONE } protected fun showProgressBar(progressValue: Int) { - (requireActivity() as DownloadWizardActivity).progressBar.apply { + requireWizardActivity().progressBar.apply { visibility = View.VISIBLE if (progressValue >= 0) { isIndeterminate = false @@ -272,7 +281,7 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() { } protected fun refreshButtons() { - (requireActivity() as DownloadWizardActivity).refreshButtons() + requireWizardActivity().refreshButtons() } open fun beforeNext() {} diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardMethodSelectFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardMethodSelectFragment.kt index 2846fd7..6574db1 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardMethodSelectFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardMethodSelectFragment.kt @@ -89,6 +89,11 @@ class DownloadWizardMethodSelectFragment : DownloadWizardActivity.DownloadWizard override fun createPrevFragment(): DownloadWizardActivity.DownloadWizardStepFragment = DownloadWizardSlotSelectFragment() + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + requireWizardActivity().getActivationCodeFromIntent()?.let(::processLpaString) + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, diff --git a/app-common/src/main/java/im/angry/openeuicc/util/ActivationCode.kt b/app-common/src/main/java/im/angry/openeuicc/util/ActivationCode.kt index 3aca0d6..df92ed5 100644 --- a/app-common/src/main/java/im/angry/openeuicc/util/ActivationCode.kt +++ b/app-common/src/main/java/im/angry/openeuicc/util/ActivationCode.kt @@ -9,15 +9,27 @@ data class ActivationCode( companion object { fun fromString(input: String): ActivationCode { val components = input.removePrefix("LPA:").split('$') + .map(String::trim) if (components.size < 2 || components[0] != "1") { throw IllegalArgumentException("Invalid activation code format") } return ActivationCode( - address = components[1].trim(), - matchingId = components.getOrNull(2)?.trim()?.ifBlank { null }, - oid = components.getOrNull(3)?.trim()?.ifBlank { null }, - confirmationCodeRequired = components.getOrNull(4)?.trim() == "1" + components[1], + components.getOrNull(2)?.ifBlank { null }, + components.getOrNull(3)?.ifBlank { null }, + components.getOrNull(4) == "1" ) } } + + override fun toString(): String { + val parts = listOf( + "1", + address, + matchingId ?: "", + oid ?: "", + if (confirmationCodeRequired) "1" else "" + ) + return parts.joinToString("$").trimEnd('$') + } } \ No newline at end of file diff --git a/app-unpriv/src/main/AndroidManifest.xml b/app-unpriv/src/main/AndroidManifest.xml index ce985cd..add82b3 100644 --- a/app-unpriv/src/main/AndroidManifest.xml +++ b/app-unpriv/src/main/AndroidManifest.xml @@ -23,6 +23,24 @@ + + + + + + + + + + + + + Date: Wed, 5 Mar 2025 23:51:21 +0800 Subject: [PATCH 02/15] fix: prev problem --- app-common/src/main/AndroidManifest.xml | 16 +++++++++++++++- .../ui/wizard/DownloadWizardActivity.kt | 1 + .../ui/wizard/DownloadWizardDetailsFragment.kt | 8 ++++++-- app-unpriv/src/main/AndroidManifest.xml | 18 ------------------ 4 files changed, 22 insertions(+), 21 deletions(-) diff --git a/app-common/src/main/AndroidManifest.xml b/app-common/src/main/AndroidManifest.xml index f53e6ff..01924f7 100644 --- a/app-common/src/main/AndroidManifest.xml +++ b/app-common/src/main/AndroidManifest.xml @@ -30,7 +30,21 @@ + android:label="@string/download_wizard"> + + + + + + + + + + + + + + - - - - - - - - - - - - - Date: Wed, 5 Mar 2025 23:52:28 +0800 Subject: [PATCH 03/15] chore: cleanup imports --- .../java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt | 1 - 1 file changed, 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 2d565af..1f5b97b 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 @@ -1,6 +1,5 @@ package im.angry.openeuicc.ui.wizard -import android.net.Uri import android.os.Bundle import android.view.View import android.view.inputmethod.InputMethodManager -- 2.45.3 From 48c23e1137d33c7f3e12b99c7abda2de4b4bf98c Mon Sep 17 00:00:00 2001 From: septs Date: Thu, 6 Mar 2025 00:19:01 +0800 Subject: [PATCH 04/15] fix: validate activation code format more robustly --- .../java/im/angry/openeuicc/util/ActivationCode.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app-common/src/main/java/im/angry/openeuicc/util/ActivationCode.kt b/app-common/src/main/java/im/angry/openeuicc/util/ActivationCode.kt index df92ed5..e96c140 100644 --- a/app-common/src/main/java/im/angry/openeuicc/util/ActivationCode.kt +++ b/app-common/src/main/java/im/angry/openeuicc/util/ActivationCode.kt @@ -9,14 +9,14 @@ data class ActivationCode( companion object { fun fromString(input: String): ActivationCode { val components = input.removePrefix("LPA:").split('$') - .map(String::trim) - if (components.size < 2 || components[0] != "1") { + .map(String::trim).map { it.ifBlank { null } } + if (components.size < 2 || components[0] != "1" || components[1] == null) { throw IllegalArgumentException("Invalid activation code format") } return ActivationCode( - components[1], - components.getOrNull(2)?.ifBlank { null }, - components.getOrNull(3)?.ifBlank { null }, + components[1]!!, + components.getOrNull(2), + components.getOrNull(3), components.getOrNull(4) == "1" ) } -- 2.45.3 From 59e1b2160307f73460ebe01cc8584c364c52cf92 Mon Sep 17 00:00:00 2001 From: septs Date: Thu, 6 Mar 2025 09:47:25 +0800 Subject: [PATCH 05/15] feat: add `lpa:` scheme supports --- app-common/src/main/AndroidManifest.xml | 13 +++++++++++++ .../openeuicc/ui/wizard/DownloadWizardActivity.kt | 7 +++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/app-common/src/main/AndroidManifest.xml b/app-common/src/main/AndroidManifest.xml index 01924f7..ec8704f 100644 --- a/app-common/src/main/AndroidManifest.xml +++ b/app-common/src/main/AndroidManifest.xml @@ -31,6 +31,19 @@ android:exported="true" android:name="im.angry.openeuicc.ui.wizard.DownloadWizardActivity" android:label="@string/download_wizard"> + + + + + + + + + + + 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 1f5b97b..82671a3 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 @@ -238,8 +238,11 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() { internal fun getActivationCodeFromIntent(): String? { val uri = intent.data ?: return null - if (uri.scheme != "openeuicc" || uri.host != "lpa") return null - return uri.path?.drop(1) + return when { + uri.scheme == "lpa" -> uri.schemeSpecificPart + uri.scheme == "openeuicc" && uri.host == "lpa" -> uri.path?.drop(1) + else -> null + } } abstract class DownloadWizardStepFragment : Fragment(), OpenEuiccContextMarker { -- 2.45.3 From eaedaed62a73fc18ba74899275675e5c8c812062 Mon Sep 17 00:00:00 2001 From: septs Date: Thu, 6 Mar 2025 10:26:20 +0800 Subject: [PATCH 06/15] chore: accept reviews --- app-common/src/main/AndroidManifest.xml | 14 -------------- .../openeuicc/ui/wizard/DownloadWizardActivity.kt | 7 ++----- 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/app-common/src/main/AndroidManifest.xml b/app-common/src/main/AndroidManifest.xml index ec8704f..bea43d7 100644 --- a/app-common/src/main/AndroidManifest.xml +++ b/app-common/src/main/AndroidManifest.xml @@ -43,20 +43,6 @@ android:scheme="lpa" android:sspPrefix="1$" /> - - - - - - - - - - - - - - uri.schemeSpecificPart - uri.scheme == "openeuicc" && uri.host == "lpa" -> uri.path?.drop(1) - else -> null - } + if (uri.scheme != "lpa") return null + return uri.schemeSpecificPart } abstract class DownloadWizardStepFragment : Fragment(), OpenEuiccContextMarker { -- 2.45.3 From 610105b14fa713d535685bb17ef90b4eb382e1ab Mon Sep 17 00:00:00 2001 From: septs Date: Thu, 6 Mar 2025 11:11:30 +0800 Subject: [PATCH 07/15] feat: stricted activation code format checking --- .../im/angry/openeuicc/util/ActivationCode.kt | 48 +++++++++++++++++-- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/app-common/src/main/java/im/angry/openeuicc/util/ActivationCode.kt b/app-common/src/main/java/im/angry/openeuicc/util/ActivationCode.kt index e96c140..9a63ac6 100644 --- a/app-common/src/main/java/im/angry/openeuicc/util/ActivationCode.kt +++ b/app-common/src/main/java/im/angry/openeuicc/util/ActivationCode.kt @@ -17,13 +17,19 @@ data class ActivationCode( components[1]!!, components.getOrNull(2), components.getOrNull(3), - components.getOrNull(4) == "1" + components.getOrNull(4) == "1", ) } } + init { + check(isFQDN(address)) { "Invalid SM-DP+ address" } + check(isMatchingID(matchingId)) { "Invalid Matching ID" } + check(isObjectIdentifier(oid)) { "Invalid OID" } + } + override fun toString(): String { - val parts = listOf( + val parts = arrayOf( "1", address, matchingId ?: "", @@ -32,4 +38,40 @@ data class ActivationCode( ) return parts.joinToString("$").trimEnd('$') } -} \ No newline at end of file +} + +/** + * SGP.22 4.1 Activation Code (v2.2.2, p111) + * + * FQDN (Fully Qualified Domain Name) of the SM-DP+ (e.g., SMDP.GSMA.COM) + * restricted to the Alphanumeric mode character set defined in table 5 of ISO/IEC 18004 [15] + * excluding '$' + */ +private fun isFQDN(input: String): Boolean { + if (input.isEmpty() || input.length > 255) return false + for (part in input.split('.')) { + if (part.isEmpty() || part.length > 63) return false + if (part.all { it.isLetterOrDigit() || it == '-' }) continue + return false + } + return true +} + +/** + * SGP.22 4.1.1 Matching ID (v2.2.2, p112) + * + * Matching ID is a string of alphanumeric characters and hyphens. + */ +private fun isMatchingID(input: String?): Boolean { + if (input == null) return true + return input.all { it.isLetterOrDigit() || it == '-' } +} + +/** + * SM-DP+ OID in the CERT.DPauth.ECDSA + */ +private fun isObjectIdentifier(input: String?): Boolean { + if (input == null) return true + if (input.length > 255) return false + return input.split('.').all { it.all(Char::isDigit) } +} -- 2.45.3 From 9c1e6959c0a1ee4839526b45c98f801ce8d36d75 Mon Sep 17 00:00:00 2001 From: septs Date: Thu, 6 Mar 2025 11:15:33 +0800 Subject: [PATCH 08/15] feat: stricted activation code format checking --- .../java/im/angry/openeuicc/util/ActivationCode.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app-common/src/main/java/im/angry/openeuicc/util/ActivationCode.kt b/app-common/src/main/java/im/angry/openeuicc/util/ActivationCode.kt index 9a63ac6..63e1f3c 100644 --- a/app-common/src/main/java/im/angry/openeuicc/util/ActivationCode.kt +++ b/app-common/src/main/java/im/angry/openeuicc/util/ActivationCode.kt @@ -49,7 +49,9 @@ data class ActivationCode( */ private fun isFQDN(input: String): Boolean { if (input.isEmpty() || input.length > 255) return false - for (part in input.split('.')) { + val parts = input.split('.') + if (parts.size < 2) return false + for (part in parts) { if (part.isEmpty() || part.length > 63) return false if (part.all { it.isLetterOrDigit() || it == '-' }) continue return false @@ -68,10 +70,14 @@ private fun isMatchingID(input: String?): Boolean { } /** + * SGP.22 4.1 Activation Code (v2.2.2, p111) + * * SM-DP+ OID in the CERT.DPauth.ECDSA */ private fun isObjectIdentifier(input: String?): Boolean { if (input == null) return true if (input.length > 255) return false - return input.split('.').all { it.all(Char::isDigit) } + val parts = input.split('.') + if (parts.size < 2) return false + return parts.all { it.all(Char::isDigit) } } -- 2.45.3 From 928833e434c32a06b969a562ffab155834071342 Mon Sep 17 00:00:00 2001 From: septs Date: Fri, 7 Mar 2025 03:00:22 +0800 Subject: [PATCH 09/15] docs: add iana reference --- app-common/src/main/AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app-common/src/main/AndroidManifest.xml b/app-common/src/main/AndroidManifest.xml index bea43d7..921da96 100644 --- a/app-common/src/main/AndroidManifest.xml +++ b/app-common/src/main/AndroidManifest.xml @@ -39,6 +39,7 @@ + -- 2.45.3 From 4fc802309bd0ebd1a13f5445823d2c496f3754bb Mon Sep 17 00:00:00 2001 From: septs Date: Fri, 7 Mar 2025 04:36:12 +0800 Subject: [PATCH 10/15] feat: stricted activation code format checking --- .../wizard/DownloadWizardDetailsFragment.kt | 10 ++++-- .../DownloadWizardMethodSelectFragment.kt | 35 +++++++++++-------- .../im/angry/openeuicc/util/ActivationCode.kt | 22 +++++------- 3 files changed, 36 insertions(+), 31 deletions(-) diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardDetailsFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardDetailsFragment.kt index 1e6045d..4db88e1 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardDetailsFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardDetailsFragment.kt @@ -1,13 +1,13 @@ package im.angry.openeuicc.ui.wizard import android.os.Bundle -import android.util.Patterns import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.widget.addTextChangedListener import com.google.android.material.textfield.TextInputLayout import im.angry.openeuicc.common.R +import im.angry.openeuicc.util.ActivationCode class DownloadWizardDetailsFragment : DownloadWizardActivity.DownloadWizardStepFragment() { private var inputComplete = false @@ -73,7 +73,13 @@ class DownloadWizardDetailsFragment : DownloadWizardActivity.DownloadWizardStepF } private fun updateInputCompleteness() { - inputComplete = Patterns.DOMAIN_NAME.matcher(smdp.editText!!.text).matches() + val result = runCatching { + ActivationCode( + smdp.editText!!.text.toString(), + matchingId.editText!!.text.toString(), + ) + } + inputComplete = result.isSuccess refreshButtons() } } \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardMethodSelectFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardMethodSelectFragment.kt index 6574db1..6d86f6c 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardMethodSelectFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardMethodSelectFragment.kt @@ -4,6 +4,7 @@ import android.app.AlertDialog import android.content.ClipboardManager import android.graphics.BitmapFactory import android.os.Bundle +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -21,7 +22,9 @@ import com.journeyapps.barcodescanner.ScanOptions import im.angry.openeuicc.common.R import im.angry.openeuicc.util.* import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext class DownloadWizardMethodSelectFragment : DownloadWizardActivity.DownloadWizardStepFragment() { @@ -130,28 +133,30 @@ class DownloadWizardMethodSelectFragment : DownloadWizardActivity.DownloadWizard } private fun processLpaString(input: String) { + if (runBlocking { preferenceRepository.verboseLoggingFlow.first() }) { + Log.i(TAG, "Processing LPA string: $input") + } try { val parsed = ActivationCode.fromString(input) state.smdp = parsed.address state.matchingId = parsed.matchingId if (parsed.confirmationCodeRequired) { - AlertDialog.Builder(requireContext()).apply { - setTitle(R.string.profile_download_required_confirmation_code) - setMessage(R.string.profile_download_required_confirmation_code_message) - setCancelable(true) - setPositiveButton(android.R.string.ok, null) - show() - } + AlertDialog.Builder(requireContext()) + .setTitle(R.string.profile_download_required_confirmation_code) + .setMessage(R.string.profile_download_required_confirmation_code_message) + .setCancelable(true) + .setPositiveButton(android.R.string.ok, null) + .show() } gotoNextFragment(DownloadWizardDetailsFragment()) - } catch (e: IllegalArgumentException) { - AlertDialog.Builder(requireContext()).apply { - setTitle(R.string.profile_download_incorrect_lpa_string) - setMessage(R.string.profile_download_incorrect_lpa_string_message) - setCancelable(true) - setNegativeButton(android.R.string.cancel, null) - show() - } + } catch (e: IllegalStateException) { + Log.d(TAG, "Failed to parse LPA string", e) + AlertDialog.Builder(requireContext()) + .setTitle(R.string.profile_download_incorrect_lpa_string) + .setMessage(R.string.profile_download_incorrect_lpa_string_message) + .setCancelable(true) + .setNegativeButton(android.R.string.cancel, null) + .show() } } diff --git a/app-common/src/main/java/im/angry/openeuicc/util/ActivationCode.kt b/app-common/src/main/java/im/angry/openeuicc/util/ActivationCode.kt index 63e1f3c..53021e5 100644 --- a/app-common/src/main/java/im/angry/openeuicc/util/ActivationCode.kt +++ b/app-common/src/main/java/im/angry/openeuicc/util/ActivationCode.kt @@ -24,8 +24,8 @@ data class ActivationCode( init { check(isFQDN(address)) { "Invalid SM-DP+ address" } - check(isMatchingID(matchingId)) { "Invalid Matching ID" } - check(isObjectIdentifier(oid)) { "Invalid OID" } + check(matchingId == null || isMatchingID(matchingId)) { "Invalid Matching ID" } + check(oid == null || isObjectIdentifier(oid)) { "Invalid OID" } } override fun toString(): String { @@ -47,7 +47,7 @@ data class ActivationCode( * restricted to the Alphanumeric mode character set defined in table 5 of ISO/IEC 18004 [15] * excluding '$' */ -private fun isFQDN(input: String): Boolean { +private fun isFQDN(input: CharSequence): Boolean { if (input.isEmpty() || input.length > 255) return false val parts = input.split('.') if (parts.size < 2) return false @@ -64,20 +64,14 @@ private fun isFQDN(input: String): Boolean { * * Matching ID is a string of alphanumeric characters and hyphens. */ -private fun isMatchingID(input: String?): Boolean { - if (input == null) return true - return input.all { it.isLetterOrDigit() || it == '-' } -} +private fun isMatchingID(input: CharSequence) = + input.all { it.isLetterOrDigit() || it == '-' } /** * SGP.22 4.1 Activation Code (v2.2.2, p111) * * SM-DP+ OID in the CERT.DPauth.ECDSA */ -private fun isObjectIdentifier(input: String?): Boolean { - if (input == null) return true - if (input.length > 255) return false - val parts = input.split('.') - if (parts.size < 2) return false - return parts.all { it.all(Char::isDigit) } -} +private fun isObjectIdentifier(input: CharSequence) = input.length < 255 && + input.count { it == '.' } > 2 && + input.split('.').all { it.isNotEmpty() && it.all(Char::isDigit) } -- 2.45.3 From b57615480b60f181e3aa5be63080da05ecadfc33 Mon Sep 17 00:00:00 2001 From: septs Date: Fri, 7 Mar 2025 06:43:56 +0800 Subject: [PATCH 11/15] revert: updateInputCompleteness --- .../ui/wizard/DownloadWizardDetailsFragment.kt | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardDetailsFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardDetailsFragment.kt index 4db88e1..1e6045d 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardDetailsFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardDetailsFragment.kt @@ -1,13 +1,13 @@ package im.angry.openeuicc.ui.wizard import android.os.Bundle +import android.util.Patterns import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.widget.addTextChangedListener import com.google.android.material.textfield.TextInputLayout import im.angry.openeuicc.common.R -import im.angry.openeuicc.util.ActivationCode class DownloadWizardDetailsFragment : DownloadWizardActivity.DownloadWizardStepFragment() { private var inputComplete = false @@ -73,13 +73,7 @@ class DownloadWizardDetailsFragment : DownloadWizardActivity.DownloadWizardStepF } private fun updateInputCompleteness() { - val result = runCatching { - ActivationCode( - smdp.editText!!.text.toString(), - matchingId.editText!!.text.toString(), - ) - } - inputComplete = result.isSuccess + inputComplete = Patterns.DOMAIN_NAME.matcher(smdp.editText!!.text).matches() refreshButtons() } } \ No newline at end of file -- 2.45.3 From cd81589691eee7b7da6288fa5dacf13ea6ed2ec2 Mon Sep 17 00:00:00 2001 From: septs Date: Fri, 7 Mar 2025 15:38:15 +0800 Subject: [PATCH 12/15] fix: exception --- .../src/main/java/im/angry/openeuicc/util/ActivationCode.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app-common/src/main/java/im/angry/openeuicc/util/ActivationCode.kt b/app-common/src/main/java/im/angry/openeuicc/util/ActivationCode.kt index 53021e5..fd8156b 100644 --- a/app-common/src/main/java/im/angry/openeuicc/util/ActivationCode.kt +++ b/app-common/src/main/java/im/angry/openeuicc/util/ActivationCode.kt @@ -11,7 +11,7 @@ data class ActivationCode( val components = input.removePrefix("LPA:").split('$') .map(String::trim).map { it.ifBlank { null } } if (components.size < 2 || components[0] != "1" || components[1] == null) { - throw IllegalArgumentException("Invalid activation code format") + throw IllegalStateException("Invalid activation code format") } return ActivationCode( components[1]!!, -- 2.45.3 From a9479d910645b29799641f4afba949befa4cf9b0 Mon Sep 17 00:00:00 2001 From: septs Date: Fri, 7 Mar 2025 15:44:26 +0800 Subject: [PATCH 13/15] feat: improve lpa parsing compatibility --- .../java/im/angry/openeuicc/util/ActivationCode.kt | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/app-common/src/main/java/im/angry/openeuicc/util/ActivationCode.kt b/app-common/src/main/java/im/angry/openeuicc/util/ActivationCode.kt index fd8156b..fbecfae 100644 --- a/app-common/src/main/java/im/angry/openeuicc/util/ActivationCode.kt +++ b/app-common/src/main/java/im/angry/openeuicc/util/ActivationCode.kt @@ -7,14 +7,13 @@ data class ActivationCode( val confirmationCodeRequired: Boolean = false, ) { companion object { - fun fromString(input: String): ActivationCode { - val components = input.removePrefix("LPA:").split('$') - .map(String::trim).map { it.ifBlank { null } } - if (components.size < 2 || components[0] != "1" || components[1] == null) { - throw IllegalStateException("Invalid activation code format") - } + fun fromString(token: String): ActivationCode { + val input = if (token.startsWith("LPA:", true)) token.drop(4) else token + val components = input.split('$').map { it.trim().ifEmpty { null } } + check(components.size >= 2) { "Invalid activation code format" } + check(components[0] == "1") { "Invalid activation code version" } return ActivationCode( - components[1]!!, + checkNotNull(components[1]) { "Invalid SM-DP+ address" }, components.getOrNull(2), components.getOrNull(3), components.getOrNull(4) == "1", -- 2.45.3 From c706b8ab36ba0c3a7faf512d783188ec01d6f3b5 Mon Sep 17 00:00:00 2001 From: septs Date: Fri, 7 Mar 2025 18:45:44 +0800 Subject: [PATCH 14/15] feat: add support for uppercase LPA scheme in intent filter --- app-common/src/main/AndroidManifest.xml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app-common/src/main/AndroidManifest.xml b/app-common/src/main/AndroidManifest.xml index 921da96..a05ba7e 100644 --- a/app-common/src/main/AndroidManifest.xml +++ b/app-common/src/main/AndroidManifest.xml @@ -31,7 +31,7 @@ android:exported="true" android:name="im.angry.openeuicc.ui.wizard.DownloadWizardActivity" android:label="@string/download_wizard"> - + @@ -43,6 +43,9 @@ + -- 2.45.3 From befdf569161507602d5051b7257cfcff8cf78c0e Mon Sep 17 00:00:00 2001 From: septs Date: Sat, 8 Mar 2025 17:36:06 +0800 Subject: [PATCH 15/15] chore: add TAG --- .../openeuicc/ui/wizard/DownloadWizardMethodSelectFragment.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardMethodSelectFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardMethodSelectFragment.kt index 6d86f6c..c492f03 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardMethodSelectFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardMethodSelectFragment.kt @@ -28,6 +28,10 @@ import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext class DownloadWizardMethodSelectFragment : DownloadWizardActivity.DownloadWizardStepFragment() { + companion object { + const val TAG = "DownloadWizardMethodSelectFragment" + } + data class DownloadMethod( val iconRes: Int, val titleRes: Int, -- 2.45.3