From c528962f29cf8087838550ed46565d664807dba4 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sat, 8 Mar 2025 15:40:30 -0500 Subject: [PATCH 01/14] wizard: Accept deep-links with the LPA: schema Co-authored-by: septs --- app-common/src/main/AndroidManifest.xml | 15 +++++++- .../ui/wizard/DownloadWizardActivity.kt | 35 ++++++++++++++----- .../wizard/DownloadWizardDetailsFragment.kt | 6 +++- .../DownloadWizardSlotSelectFragment.kt | 6 +++- 4 files changed, 50 insertions(+), 12 deletions(-) diff --git a/app-common/src/main/AndroidManifest.xml b/app-common/src/main/AndroidManifest.xml index f53e6ff..6edaaf1 100644 --- a/app-common/src/main/AndroidManifest.xml +++ b/app-common/src/main/AndroidManifest.xml @@ -30,7 +30,20 @@ + android:label="@string/download_wizard"> + + + + + + + + + + + + + Date: Sat, 8 Mar 2025 15:45:58 -0500 Subject: [PATCH 02/14] ActivationCode::fromString -> ActivationCode::parse Un-confusion :D --- .../angry/openeuicc/ui/wizard/DownloadWizardActivity.kt | 3 +-- .../ui/wizard/DownloadWizardMethodSelectFragment.kt | 2 +- .../main/java/im/angry/openeuicc/util/ActivationCode.kt | 8 ++++---- 3 files changed, 6 insertions(+), 7 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 01a4d7c..44b7479 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 @@ -21,7 +21,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() { @@ -123,7 +122,7 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() { // but that _is_ the desired behavior. val uri = intent.data if (uri?.scheme == "lpa") { - val parsed = ActivationCode.fromString(uri.schemeSpecificPart) + val parsed = ActivationCode.parse(uri.schemeSpecificPart) state.smdp = parsed.address state.matchingId = parsed.matchingId state.skipMethodSelect = true 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..452f1e1 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 @@ -126,7 +126,7 @@ class DownloadWizardMethodSelectFragment : DownloadWizardActivity.DownloadWizard private fun processLpaString(input: String) { try { - val parsed = ActivationCode.fromString(input) + val parsed = ActivationCode.parse(input) state.smdp = parsed.address state.matchingId = parsed.matchingId if (parsed.confirmationCodeRequired) { 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 c21e837..2399b5a 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 @@ -2,12 +2,12 @@ package im.angry.openeuicc.util data class ActivationCode( val address: String, - val matchingId: String? = null, - val oid: String? = null, - val confirmationCodeRequired: Boolean = false, + val matchingId: String?, + val oid: String?, + val confirmationCodeRequired: Boolean, ) { companion object { - fun fromString(input: String): ActivationCode { + fun parse(input: String): ActivationCode { val components = input.removePrefix("LPA:").split('$') if (components.size < 2 || components[0] != "1") { throw IllegalArgumentException("Invalid activation code format") From 7edde1ffa438a70c1910126a8551ce6338dc81af Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sat, 8 Mar 2025 16:05:00 -0500 Subject: [PATCH 03/14] wizard: Rework handling for confirmation code from LPA strings There are more than one way to acquire an LPA string here. Let's just store whether we need confirmation code as a boolean in state and then use that to decide whether it is actually required in the step for inputting details. --- .../ui/wizard/DownloadWizardActivity.kt | 8 +++++++- .../wizard/DownloadWizardDetailsFragment.kt | 16 ++++++++++++++++ .../DownloadWizardMethodSelectFragment.kt | 19 ++++++++----------- app-common/src/main/res/values-ja/strings.xml | 3 +-- .../src/main/res/values-zh-rCN/strings.xml | 3 +-- .../src/main/res/values-zh-rTW/strings.xml | 1 + app-common/src/main/res/values/strings.xml | 3 +-- 7 files changed, 35 insertions(+), 18 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 44b7479..dfbe17d 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 @@ -35,6 +35,7 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() { var downloadTaskID: Long, var downloadError: LocalProfileAssistant.ProfileDownloadException?, var skipMethodSelect: Boolean, + var confirmationCodeRequired: Boolean, ) private lateinit var state: DownloadWizardState @@ -72,7 +73,8 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() { downloadStarted = false, downloadTaskID = -1, downloadError = null, - skipMethodSelect = false + skipMethodSelect = false, + confirmationCodeRequired = false, ) handleDeepLink() @@ -125,6 +127,7 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() { val parsed = ActivationCode.parse(uri.schemeSpecificPart) state.smdp = parsed.address state.matchingId = parsed.matchingId + state.confirmationCodeRequired = parsed.confirmationCodeRequired state.skipMethodSelect = true } } @@ -154,6 +157,7 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() { outState.putString("imei", state.imei) outState.putBoolean("downloadStarted", state.downloadStarted) outState.putLong("downloadTaskID", state.downloadTaskID) + outState.putBoolean("confirmationCodeRequired", state.confirmationCodeRequired) } override fun onRestoreInstanceState(savedInstanceState: Bundle) { @@ -170,6 +174,8 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() { state.downloadStarted = savedInstanceState.getBoolean("downloadStarted", state.downloadStarted) state.downloadTaskID = savedInstanceState.getLong("downloadTaskID", state.downloadTaskID) + state.confirmationCode = savedInstanceState.getString("confirmationCode", state.confirmationCode) + state.confirmationCodeRequired = savedInstanceState.getBoolean("confirmationCodeRequired", state.confirmationCodeRequired) } private fun onPrevPressed() { 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 4a852ec..402e7a5 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 @@ -5,6 +5,7 @@ import android.util.Patterns import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.Toast import androidx.core.widget.addTextChangedListener import com.google.android.material.textfield.TextInputLayout import im.angry.openeuicc.common.R @@ -55,6 +56,9 @@ class DownloadWizardDetailsFragment : DownloadWizardActivity.DownloadWizardStepF smdp.editText!!.addTextChangedListener { updateInputCompleteness() } + confirmationCode.editText!!.addTextChangedListener { + updateInputCompleteness() + } return view } @@ -65,6 +69,15 @@ class DownloadWizardDetailsFragment : DownloadWizardActivity.DownloadWizardStepF confirmationCode.editText!!.setText(state.confirmationCode) imei.editText!!.setText(state.imei) updateInputCompleteness() + + if (state.confirmationCodeRequired) { + confirmationCode.editText!!.requestFocus() + confirmationCode.editText!!.hint = + getString(R.string.profile_download_confirmation_code_required) + } else { + confirmationCode.editText!!.hint = + getString(R.string.profile_download_confirmation_code) + } } override fun onPause() { @@ -74,6 +87,9 @@ class DownloadWizardDetailsFragment : DownloadWizardActivity.DownloadWizardStepF private fun updateInputCompleteness() { inputComplete = Patterns.DOMAIN_NAME.matcher(smdp.editText!!.text).matches() + if (state.confirmationCodeRequired) { + inputComplete = inputComplete && confirmationCode.editText!!.text.isNotEmpty() + } 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 452f1e1..85d75b3 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 @@ -129,15 +129,7 @@ class DownloadWizardMethodSelectFragment : DownloadWizardActivity.DownloadWizard val parsed = ActivationCode.parse(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() - } - } + state.confirmationCodeRequired = parsed.confirmationCodeRequired gotoNextFragment(DownloadWizardDetailsFragment()) } catch (e: IllegalArgumentException) { AlertDialog.Builder(requireContext()).apply { @@ -150,14 +142,19 @@ class DownloadWizardMethodSelectFragment : DownloadWizardActivity.DownloadWizard } } - private class DownloadMethodViewHolder(private val root: View) : ViewHolder(root) { + private inner class DownloadMethodViewHolder(private val root: View) : ViewHolder(root) { private val icon = root.requireViewById(R.id.download_method_icon) private val title = root.requireViewById(R.id.download_method_title) fun bind(item: DownloadMethod) { icon.setImageResource(item.iconRes) title.setText(item.titleRes) - root.setOnClickListener { item.onClick() } + root.setOnClickListener { + // If the user elected to use another download method, reset the confirmation code flag + // too + state.confirmationCodeRequired = false + item.onClick() + } } } diff --git a/app-common/src/main/res/values-ja/strings.xml b/app-common/src/main/res/values-ja/strings.xml index e4969c1..df72674 100644 --- a/app-common/src/main/res/values-ja/strings.xml +++ b/app-common/src/main/res/values-ja/strings.xml @@ -42,12 +42,11 @@ サーバー (RSP / SM-DP+) アクティベーションコード 確認コード (オプション) + 確認コード (必須) IMEI (オプション) ダウンロードに失敗する可能性があります 残り容量が少ないため、ダウンロードに失敗する可能性があります。 クリップボードに LPA コードがありません - 確認コードが必要です - クリップボードからスキャンした QR コードまたは LPA コードに必要な確認コードを入力してください。 解析できません QR コードまたはクリップボードの内容を LPA コードとして解析できませんでした。 ダウンロードウィザード diff --git a/app-common/src/main/res/values-zh-rCN/strings.xml b/app-common/src/main/res/values-zh-rCN/strings.xml index 2cadc03..de02046 100644 --- a/app-common/src/main/res/values-zh-rCN/strings.xml +++ b/app-common/src/main/res/values-zh-rCN/strings.xml @@ -37,6 +37,7 @@ 服务器 (RSP / SM-DP+) 激活码 确认码 (可选) + 确认码 (必需) IMEI (可选) 本次下载可能会失败 当前芯片的剩余空间不足,可能导致配置下载失败。\n是否继续下载? @@ -144,6 +145,4 @@ 无视 SM-DP+ 的 TLS 证书 允许 RSP 服务器使用任意证书 无信息 - 需要确认码 - 您扫描的二维码或粘贴的 LPA 码需要一个额外的确认码 \ No newline at end of file diff --git a/app-common/src/main/res/values-zh-rTW/strings.xml b/app-common/src/main/res/values-zh-rTW/strings.xml index 28691a0..d133d2b 100644 --- a/app-common/src/main/res/values-zh-rTW/strings.xml +++ b/app-common/src/main/res/values-zh-rTW/strings.xml @@ -37,6 +37,7 @@ 伺服器 (RSP / SM-DP+) 啟用碼 確認碼 (可選) + 確認碼 (必需) IMEI (可選) 本次下載可能會失敗 目前晶片的剩餘空間不足,可能導致配置下載失敗。\n是否繼續下載? diff --git a/app-common/src/main/res/values/strings.xml b/app-common/src/main/res/values/strings.xml index a45ce1f..990e629 100644 --- a/app-common/src/main/res/values/strings.xml +++ b/app-common/src/main/res/values/strings.xml @@ -53,13 +53,12 @@ Server (RSP / SM-DP+) Activation Code Confirmation Code (Optional) + Confirmation Code (Required) IMEI (Optional) This download may fail This download may fail due to low remaining capacity. No LPA code found in clipboard - Confirmation Code Required - Please provide a confirmation code as required by the scanned QR code or LPA code from clipboard. Unable to parse Could not parse QR code or clipboard content as a LPA code. From 2b86d719ddcc34ee1e3597e3992a3d522891fb71 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sat, 8 Mar 2025 16:07:41 -0500 Subject: [PATCH 04/14] ActivationCode -> LPAString Un-confuse myself.... The term "ActivationCode" is too overloaded even within OpenEUICC itself. --- .../im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt | 4 ++-- .../ui/wizard/DownloadWizardMethodSelectFragment.kt | 2 +- .../openeuicc/util/{ActivationCode.kt => LPAString.kt} | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) rename app-common/src/main/java/im/angry/openeuicc/util/{ActivationCode.kt => LPAString.kt} (89%) 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 dfbe17d..a9f868f 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 @@ -124,7 +124,7 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() { // but that _is_ the desired behavior. val uri = intent.data if (uri?.scheme == "lpa") { - val parsed = ActivationCode.parse(uri.schemeSpecificPart) + val parsed = LPAString.parse(uri.schemeSpecificPart) state.smdp = parsed.address state.matchingId = parsed.matchingId state.confirmationCodeRequired = parsed.confirmationCodeRequired @@ -135,7 +135,7 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() { override fun onProvideAssistContent(outContent: AssistContent?) { super.onProvideAssistContent(outContent) outContent?.webUri = try { - val activationCode = ActivationCode( + val activationCode = LPAString( state.smdp, state.matchingId, null, 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 85d75b3..90fdb33 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 @@ -126,7 +126,7 @@ class DownloadWizardMethodSelectFragment : DownloadWizardActivity.DownloadWizard private fun processLpaString(input: String) { try { - val parsed = ActivationCode.parse(input) + val parsed = LPAString.parse(input) state.smdp = parsed.address state.matchingId = parsed.matchingId state.confirmationCodeRequired = parsed.confirmationCodeRequired diff --git a/app-common/src/main/java/im/angry/openeuicc/util/ActivationCode.kt b/app-common/src/main/java/im/angry/openeuicc/util/LPAString.kt similarity index 89% rename from app-common/src/main/java/im/angry/openeuicc/util/ActivationCode.kt rename to app-common/src/main/java/im/angry/openeuicc/util/LPAString.kt index 2399b5a..20956fb 100644 --- a/app-common/src/main/java/im/angry/openeuicc/util/ActivationCode.kt +++ b/app-common/src/main/java/im/angry/openeuicc/util/LPAString.kt @@ -1,18 +1,18 @@ package im.angry.openeuicc.util -data class ActivationCode( +data class LPAString( val address: String, val matchingId: String?, val oid: String?, val confirmationCodeRequired: Boolean, ) { companion object { - fun parse(input: String): ActivationCode { + fun parse(input: String): LPAString { val components = input.removePrefix("LPA:").split('$') if (components.size < 2 || components[0] != "1") { throw IllegalArgumentException("Invalid activation code format") } - return ActivationCode( + return LPAString( address = components[1].trim(), matchingId = components.getOrNull(2)?.trim()?.ifBlank { null }, oid = components.getOrNull(3)?.trim()?.ifBlank { null }, From 6557ce45a749ed99f9e78f0412e88b492412aafd Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sat, 8 Mar 2025 16:09:47 -0500 Subject: [PATCH 05/14] i18n: Update alert for low NVRAM --- app-common/src/main/res/values-ja/strings.xml | 2 +- app-common/src/main/res/values-zh-rCN/strings.xml | 2 +- app-common/src/main/res/values-zh-rTW/strings.xml | 2 +- app-common/src/main/res/values/strings.xml | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app-common/src/main/res/values-ja/strings.xml b/app-common/src/main/res/values-ja/strings.xml index df72674..22b8a4c 100644 --- a/app-common/src/main/res/values-ja/strings.xml +++ b/app-common/src/main/res/values-ja/strings.xml @@ -44,7 +44,7 @@ 確認コード (オプション) 確認コード (必須) IMEI (オプション) - ダウンロードに失敗する可能性があります + 残り容量が少ない 残り容量が少ないため、ダウンロードに失敗する可能性があります。 クリップボードに LPA コードがありません 解析できません diff --git a/app-common/src/main/res/values-zh-rCN/strings.xml b/app-common/src/main/res/values-zh-rCN/strings.xml index de02046..9fa27f4 100644 --- a/app-common/src/main/res/values-zh-rCN/strings.xml +++ b/app-common/src/main/res/values-zh-rCN/strings.xml @@ -39,7 +39,7 @@ 确认码 (可选) 确认码 (必需) IMEI (可选) - 本次下载可能会失败 + 剩余空间不足 当前芯片的剩余空间不足,可能导致配置下载失败。\n是否继续下载? 日志已保存到指定路径。需要通过其他 App 分享吗? 新昵称 diff --git a/app-common/src/main/res/values-zh-rTW/strings.xml b/app-common/src/main/res/values-zh-rTW/strings.xml index d133d2b..1b076b2 100644 --- a/app-common/src/main/res/values-zh-rTW/strings.xml +++ b/app-common/src/main/res/values-zh-rTW/strings.xml @@ -39,7 +39,7 @@ 確認碼 (可選) 確認碼 (必需) IMEI (可選) - 本次下載可能會失敗 + 剩餘空間不足 目前晶片的剩餘空間不足,可能導致配置下載失敗。\n是否繼續下載? 日誌已儲存到指定路徑。需要透過其他 App 分享嗎? 新名稱 diff --git a/app-common/src/main/res/values/strings.xml b/app-common/src/main/res/values/strings.xml index 990e629..d6e1781 100644 --- a/app-common/src/main/res/values/strings.xml +++ b/app-common/src/main/res/values/strings.xml @@ -56,8 +56,8 @@ Confirmation Code (Required) IMEI (Optional) - This download may fail - This download may fail due to low remaining capacity. + Low remaining capacity + This profile may fail to download due to low remaining capacity. No LPA code found in clipboard Unable to parse Could not parse QR code or clipboard content as a LPA code. From 53f9459aed7ba760b1c19ac0dec345c0815aa4cc Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sat, 8 Mar 2025 16:14:37 -0500 Subject: [PATCH 06/14] i18n: Update missing translations --- app-common/src/main/res/values-zh-rCN/strings.xml | 5 +++++ app-common/src/main/res/values-zh-rTW/strings.xml | 5 +++++ app-common/src/main/res/values/strings.xml | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app-common/src/main/res/values-zh-rCN/strings.xml b/app-common/src/main/res/values-zh-rCN/strings.xml index 9fa27f4..e255d9a 100644 --- a/app-common/src/main/res/values-zh-rCN/strings.xml +++ b/app-common/src/main/res/values-zh-rCN/strings.xml @@ -37,6 +37,11 @@ 服务器 (RSP / SM-DP+) 激活码 确认码 (可选) + 已复制序列号到剪贴板 + 产品名称 + 产品序列号 + 产品 Bootloader 版本 + 产品固件版本 确认码 (必需) IMEI (可选) 剩余空间不足 diff --git a/app-common/src/main/res/values-zh-rTW/strings.xml b/app-common/src/main/res/values-zh-rTW/strings.xml index 1b076b2..b8c94b5 100644 --- a/app-common/src/main/res/values-zh-rTW/strings.xml +++ b/app-common/src/main/res/values-zh-rTW/strings.xml @@ -37,6 +37,11 @@ 伺服器 (RSP / SM-DP+) 啟用碼 確認碼 (可選) + 已複製序號到剪貼簿 + 產品名稱 + 產品序號 + 產品引導程式版本 + 產品韌體版本 確認碼 (必需) IMEI (可選) 剩餘空間不足 diff --git a/app-common/src/main/res/values/strings.xml b/app-common/src/main/res/values/strings.xml index d6e1781..8a0ac24 100644 --- a/app-common/src/main/res/values/strings.xml +++ b/app-common/src/main/res/values/strings.xml @@ -31,7 +31,7 @@ Cannot switch to new eSIM profile. Confirmation string mismatch ICCID copied to clipboard - Serial Number copied to clipboard + Serial number copied to clipboard EID copied to clipboard ATR copied to clipboard From d3df70501a9e99d28a830558cdb7f9234861965d Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sat, 8 Mar 2025 16:27:36 -0500 Subject: [PATCH 07/14] wizard: Make sure bitmaps are recycled properly Co-authored-by: septs --- .../wizard/DownloadWizardMethodSelectFragment.kt | 15 ++++++--------- .../main/java/im/angry/openeuicc/util/Utils.kt | 7 +++++++ 2 files changed, 13 insertions(+), 9 deletions(-) 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 90fdb33..0ae330e 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 @@ -44,17 +44,14 @@ class DownloadWizardMethodSelectFragment : DownloadWizardActivity.DownloadWizard lifecycleScope.launch(Dispatchers.IO) { runCatching { - requireContext().contentResolver.openInputStream(result)?.let { input -> - val bmp = BitmapFactory.decodeStream(input) - input.close() - - decodeQrFromBitmap(bmp)?.let { - withContext(Dispatchers.Main) { - processLpaString(it) + requireContext().contentResolver.openInputStream(result)?.use { input -> + BitmapFactory.decodeStream(input).use { bmp -> + decodeQrFromBitmap(bmp)?.let { + withContext(Dispatchers.Main) { + processLpaString(it) + } } } - - bmp.recycle() } } } diff --git a/app-common/src/main/java/im/angry/openeuicc/util/Utils.kt b/app-common/src/main/java/im/angry/openeuicc/util/Utils.kt index 444c176..5a559f9 100644 --- a/app-common/src/main/java/im/angry/openeuicc/util/Utils.kt +++ b/app-common/src/main/java/im/angry/openeuicc/util/Utils.kt @@ -86,6 +86,13 @@ suspend fun connectSEService(context: Context): SEService = suspendCoroutine { c } } +inline fun Bitmap.use(f: (Bitmap) -> T): T = + try { + f(this) + } finally { + recycle() + } + fun decodeQrFromBitmap(bmp: Bitmap): String? = runCatching { val pixels = IntArray(bmp.width * bmp.height) From db8063cd5fd0347767ba77e388b0c29b53198d8a Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sat, 8 Mar 2025 16:42:48 -0500 Subject: [PATCH 08/14] wizard: Reduce nested closures --- .../wizard/DownloadWizardMethodSelectFragment.kt | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) 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 0ae330e..4b02b7a 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 @@ -42,18 +42,16 @@ class DownloadWizardMethodSelectFragment : DownloadWizardActivity.DownloadWizard registerForActivityResult(ActivityResultContracts.GetContent()) { result -> if (result == null) return@registerForActivityResult - lifecycleScope.launch(Dispatchers.IO) { - runCatching { - requireContext().contentResolver.openInputStream(result)?.use { input -> - BitmapFactory.decodeStream(input).use { bmp -> - decodeQrFromBitmap(bmp)?.let { - withContext(Dispatchers.Main) { - processLpaString(it) - } - } + lifecycleScope.launch { + val decoded = withContext(Dispatchers.IO) { + runCatching { + requireContext().contentResolver.openInputStream(result)?.use { input -> + BitmapFactory.decodeStream(input).use(::decodeQrFromBitmap) } } } + + decoded.getOrNull()?.let { processLpaString(it) } } } From ece231f17ba851f4dfec2d2770944ff3ce1303d0 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sat, 8 Mar 2025 17:05:52 -0500 Subject: [PATCH 09/14] lpac-jni: Run `euicc_http_cleanup()` on success --- libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.c b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.c index 028e30d..bae2ee8 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.c +++ b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.c @@ -126,8 +126,11 @@ Java_net_typeblog_lpac_1jni_LpacJni_downloadProfile(JNIEnv *env, jobject thiz, j syslog(LOG_INFO, "es10b_load_bound_profile_package %d, reason %d", ret, es10b_load_bound_profile_package_result.errorReason); if (ret < 0) { ret = - (int) es10b_load_bound_profile_package_result.errorReason; + goto out; } + euicc_http_cleanup(ctx); + out: // We expect Java side to call cancelSessions after any error -- thus, `euicc_http_cleanup` is done there // This is so that Java side can access the last HTTP and/or APDU errors when we return. From 17102be7cbda1c05a3e6543ad7a03677b7175b27 Mon Sep 17 00:00:00 2001 From: septs Date: Sat, 8 Mar 2025 17:25:26 -0500 Subject: [PATCH 10/14] feat: Allow forcing the use of TelephonyManager everywhere Manual merge of #139, but removed all reference to "TMAPI" because such a term does not exist. Also reworked PreferenceRepository to allow extensibility from the privileged app. --- .../im/angry/openeuicc/ui/SettingsFragment.kt | 2 +- .../im/angry/openeuicc/util/PreferenceUtils.kt | 4 ++-- app-common/src/main/res/xml/pref_settings.xml | 4 ++-- .../core/PrivilegedEuiccChannelFactory.kt | 3 ++- .../angry/openeuicc/di/PrivilegedAppContainer.kt | 5 +++++ .../openeuicc/ui/PrivilegedSettingsFragment.kt | 10 ++++++++++ .../util/PrivilegedPreferenceRepository.kt | 15 +++++++++++++++ app/src/main/res/values-ja/strings.xml | 2 ++ app/src/main/res/values-zh-rCN/strings.xml | 2 ++ app/src/main/res/values-zh-rTW/strings.xml | 2 ++ app/src/main/res/values/strings.xml | 4 ++++ app/src/main/res/xml/pref_privileged_settings.xml | 12 ++++++++++++ 12 files changed, 59 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/im/angry/openeuicc/util/PrivilegedPreferenceRepository.kt create mode 100644 app/src/main/res/xml/pref_privileged_settings.xml 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 fab680f..c54d6a1 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 @@ -122,7 +122,7 @@ open class SettingsFragment: PreferenceFragmentCompat() { return true } - private fun CheckBoxPreference.bindBooleanFlow(flow: PreferenceFlowWrapper) { + protected fun CheckBoxPreference.bindBooleanFlow(flow: PreferenceFlowWrapper) { 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 f5e3ca2..3c7bbf4 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 @@ -35,7 +35,7 @@ internal object PreferenceKeys { val IGNORE_TLS_CERTIFICATE = booleanPreferencesKey("ignore_tls_certificate") } -class PreferenceRepository(private val context: Context) { +open class PreferenceRepository(private val context: Context) { // Expose flows so that we can also handle default values // ---- Profile Notifications ---- val notificationDownloadFlow = bindFlow(PreferenceKeys.NOTIFICATION_DOWNLOAD, true) @@ -51,7 +51,7 @@ class PreferenceRepository(private val context: Context) { val unfilteredProfileListFlow = bindFlow(PreferenceKeys.UNFILTERED_PROFILE_LIST, false) val ignoreTLSCertificateFlow = bindFlow(PreferenceKeys.IGNORE_TLS_CERTIFICATE, false) - private fun bindFlow(key: Preferences.Key, defaultValue: T): PreferenceFlowWrapper = + protected fun bindFlow(key: Preferences.Key, defaultValue: T): PreferenceFlowWrapper = PreferenceFlowWrapper(context, key, defaultValue) } diff --git a/app-common/src/main/res/xml/pref_settings.xml b/app-common/src/main/res/xml/pref_settings.xml index bb5bd50..944bd27 100644 --- a/app-common/src/main/res/xml/pref_settings.xml +++ b/app-common/src/main/res/xml/pref_settings.xml @@ -52,7 +52,7 @@ - @@ -69,7 +69,7 @@ app:summary="@string/pref_developer_ignore_tls_certificate_desc" app:title="@string/pref_developer_ignore_tls_certificate" /> - + @@ -13,5 +19,9 @@ class PrivilegedSettingsFragment : SettingsFragment() { // eventually work for platform-signed apps. Or, at some point we might introduce our own // locale picker, which hopefully works whether privileged or not. requirePreference("pref_advanced_language").isVisible = false + + // Force use TelephonyManager API + requirePreference("pref_developer_tmapi_removable") + .bindBooleanFlow((preferenceRepository as PrivilegedPreferenceRepository).removableTelephonyManagerFlow) } } \ No newline at end of file diff --git a/app/src/main/java/im/angry/openeuicc/util/PrivilegedPreferenceRepository.kt b/app/src/main/java/im/angry/openeuicc/util/PrivilegedPreferenceRepository.kt new file mode 100644 index 0000000..9e1ffcc --- /dev/null +++ b/app/src/main/java/im/angry/openeuicc/util/PrivilegedPreferenceRepository.kt @@ -0,0 +1,15 @@ +package im.angry.openeuicc.util + +import android.content.Context +import androidx.datastore.preferences.core.booleanPreferencesKey + +internal object PrivilegedPreferenceKeys { + // ---- Developer Options ---- + val REMOVABLE_TELEPHONY_MANAGER = booleanPreferencesKey("removable_telephony_manager") +} + +class PrivilegedPreferenceRepository(context: Context) : PreferenceRepository(context) { + // ---- Developer Options ---- + val removableTelephonyManagerFlow = + bindFlow(PrivilegedPreferenceKeys.REMOVABLE_TELEPHONY_MANAGER, false) +} \ No newline at end of file diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 29f3ef6..acc1728 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -17,4 +17,6 @@ 使用しているデバイスは eSIM をサポートしています。モバイルネットワークに接続するには通信事業者が発行した eSIM をダウンロードするか、物理 SIM を挿入してください。 スキップ eSIM をダウンロード + TelephonyManagerをどこでも使用 + デフォルトでは、非特権モード (EasyEUICC) と一致するように、取り外し可能な eUICC に対して OMAPI のみが試行されます。これは、一部のデバイスではうまく機能しない可能性があります。このオプションを選択する場合、取り外し可能な eUICC でも TelephonyManager を使用することになります。 diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 18497b2..d6befc2 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -17,4 +17,6 @@ 跳过 下载 eSIM TelephonyManager (特权) + 全局使用 TelephonyManager + 在默认情况下,可移除 eUICC 将仅使用 OMAPI。这与非特权模式 (EasyEUICC) 一致。在某些设备上 OMAPI 可能存在问题 -- 选择此选项以强制使用 TelephonyManager。 \ No newline at end of file diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 368efbc..10285d3 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -17,4 +17,6 @@ 跳過 下載 eSIM TelephonyManager (特權) + 全域使用 TelephonyManager + 在預設情況下,可移除 eUICC 將僅使用 OMAPI。這與非特權模式 (EasyEUICC) 一致。在某些裝置上 OMAPI 可能有問題 -- 選擇此選項以強制使用 TelephonyManager。 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 47c88bd..ddf17e4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -22,4 +22,8 @@ Your device supports eSIMs. To connect to mobile network, download your eSIM issued by a carrier, or insert a physical SIM. Skip Download eSIM + + + Use TelephonyManager everywhere + By default, only OMAPI is attempted for removable eUICCs to match what is done in unprivileged mode (i.e. EasyEUICC). This may not work well on some devices. Select this option to force the use of TelephonyManager even for removable eUICCs. \ No newline at end of file diff --git a/app/src/main/res/xml/pref_privileged_settings.xml b/app/src/main/res/xml/pref_privileged_settings.xml new file mode 100644 index 0000000..339233b --- /dev/null +++ b/app/src/main/res/xml/pref_privileged_settings.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file From 5dd9eed4feebf18f0e49f155456d63f0bcbfd115 Mon Sep 17 00:00:00 2001 From: septs Date: Sat, 8 Mar 2025 20:26:30 -0500 Subject: [PATCH 11/14] feat: euicc memory reset peter: Adjusted strings and i18n translation. Also removed the arbitrary limit on USB channels -- this is a developer option anyway. --- .../service/EuiccChannelManagerService.kt | 15 +++ .../openeuicc/ui/EuiccManagementFragment.kt | 57 +++++--- .../openeuicc/ui/EuiccMemoryResetFragment.kt | 126 ++++++++++++++++++ .../im/angry/openeuicc/ui/SettingsFragment.kt | 6 + .../angry/openeuicc/util/PreferenceUtils.kt | 2 + .../res/drawable/ic_euicc_memory_reset.xml | 18 +++ .../src/main/res/menu/fragment_euicc.xml | 5 + app-common/src/main/res/values-ja/strings.xml | 12 ++ .../src/main/res/values-zh-rCN/strings.xml | 12 ++ .../src/main/res/values-zh-rTW/strings.xml | 12 ++ app-common/src/main/res/values/strings.xml | 13 ++ app-common/src/main/res/xml/pref_settings.xml | 7 + 12 files changed, 264 insertions(+), 21 deletions(-) create mode 100644 app-common/src/main/java/im/angry/openeuicc/ui/EuiccMemoryResetFragment.kt create mode 100644 app-common/src/main/res/drawable/ic_euicc_memory_reset.xml diff --git a/app-common/src/main/java/im/angry/openeuicc/service/EuiccChannelManagerService.kt b/app-common/src/main/java/im/angry/openeuicc/service/EuiccChannelManagerService.kt index 760f1af..52943d8 100644 --- a/app-common/src/main/java/im/angry/openeuicc/service/EuiccChannelManagerService.kt +++ b/app-common/src/main/java/im/angry/openeuicc/service/EuiccChannelManagerService.kt @@ -495,4 +495,19 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker { preferenceRepository.notificationSwitchFlow.first() } } + + fun launchMemoryReset(slotId: Int, portId: Int): ForegroundTaskSubscriberFlow = + launchForegroundTask( + getString(R.string.task_euicc_memory_reset), + getString(R.string.task_euicc_memory_reset_failure), + R.drawable.ic_euicc_memory_reset + ) { + euiccChannelManager.beginTrackedOperation(slotId, portId) { + euiccChannelManager.withEuiccChannel(slotId, portId) { channel -> + channel.lpa.euiccMemoryReset() + } + + preferenceRepository.euiccMemoryResetFlow.first() + } + } } \ No newline at end of file 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 842f4ec..12995ff 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 @@ -38,8 +38,10 @@ import im.angry.openeuicc.util.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener, @@ -55,6 +57,7 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener, private lateinit var fab: FloatingActionButton private lateinit var profileList: RecyclerView private var logicalSlotId: Int = -1 + private lateinit var eid: String private val adapter = EuiccProfileAdapter() @@ -131,31 +134,42 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener, inflater.inflate(R.menu.fragment_euicc, menu) } - override fun onOptionsItemSelected(item: MenuItem): Boolean = - when (item.itemId) { - R.id.show_notifications -> { - if (logicalSlotId != -1) { - Intent(requireContext(), NotificationsActivity::class.java).apply { - putExtra("logicalSlotId", logicalSlotId) - startActivity(this) - } - } - true - } + override fun onPrepareOptionsMenu(menu: Menu) { + super.onPrepareOptionsMenu(menu) + menu.findItem(R.id.show_notifications).isVisible = + logicalSlotId != -1 + menu.findItem(R.id.euicc_info).isVisible = + logicalSlotId != -1 + menu.findItem(R.id.euicc_memory_reset).isVisible = + runBlocking { preferenceRepository.euiccMemoryResetFlow.first() } + } - R.id.euicc_info -> { - if (logicalSlotId != -1) { - Intent(requireContext(), EuiccInfoActivity::class.java).apply { - putExtra("logicalSlotId", logicalSlotId) - startActivity(this) - } - } - true + override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { + R.id.show_notifications -> { + Intent(requireContext(), NotificationsActivity::class.java).apply { + putExtra("logicalSlotId", logicalSlotId) + startActivity(this) } - - else -> super.onOptionsItemSelected(item) + true } + R.id.euicc_info -> { + Intent(requireContext(), EuiccInfoActivity::class.java).apply { + putExtra("logicalSlotId", logicalSlotId) + startActivity(this) + } + true + } + + R.id.euicc_memory_reset -> { + EuiccMemoryResetFragment.newInstance(slotId, portId, eid) + .show(childFragmentManager, EuiccMemoryResetFragment.TAG) + true + } + + else -> super.onOptionsItemSelected(item) + } + protected open suspend fun onCreateFooterViews( parent: ViewGroup, profiles: List @@ -192,6 +206,7 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener, val profiles = withEuiccChannel { channel -> logicalSlotId = channel.logicalSlotId + eid = channel.lpa.eID euiccChannelManager.notifyEuiccProfilesChanged(channel.logicalSlotId) if (unfilteredProfileListFlow.value) channel.lpa.profiles diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/EuiccMemoryResetFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/EuiccMemoryResetFragment.kt new file mode 100644 index 0000000..086a849 --- /dev/null +++ b/app-common/src/main/java/im/angry/openeuicc/ui/EuiccMemoryResetFragment.kt @@ -0,0 +1,126 @@ +package im.angry.openeuicc.ui + +import android.graphics.Typeface +import android.os.Bundle +import android.text.Editable +import android.util.Log +import android.widget.EditText +import android.widget.Toast +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope +import im.angry.openeuicc.common.R +import im.angry.openeuicc.service.EuiccChannelManagerService.Companion.waitDone +import im.angry.openeuicc.util.EuiccChannelFragmentMarker +import im.angry.openeuicc.util.EuiccProfilesChangedListener +import im.angry.openeuicc.util.ensureEuiccChannelManager +import im.angry.openeuicc.util.euiccChannelManagerService +import im.angry.openeuicc.util.newInstanceEuicc +import im.angry.openeuicc.util.notifyEuiccProfilesChanged +import im.angry.openeuicc.util.portId +import im.angry.openeuicc.util.slotId +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.launch + +class EuiccMemoryResetFragment : DialogFragment(), EuiccChannelFragmentMarker { + companion object { + const val TAG = "EuiccMemoryResetFragment" + + private const val FIELD_EID = "eid" + + fun newInstance(slotId: Int, portId: Int, eid: String) = + newInstanceEuicc(EuiccMemoryResetFragment::class.java, slotId, portId) { + putString(FIELD_EID, eid) + } + } + + private val eid: String by lazy { requireArguments().getString(FIELD_EID)!! } + + private val confirmText: String by lazy { + getString(R.string.euicc_memory_reset_confirm_text, eid.takeLast(8)) + } + + private inline val isMatched: Boolean + get() = editText.text.toString() == confirmText + + private var confirmed = false + + private var toast: Toast? = null + set(value) { + toast?.cancel() + field = value + value?.show() + } + + private val editText by lazy { + EditText(requireContext()).apply { + isLongClickable = false + typeface = Typeface.MONOSPACE + hint = Editable.Factory.getInstance() + .newEditable(getString(R.string.euicc_memory_reset_hint_text, confirmText)) + } + } + + private inline val alertDialog: AlertDialog + get() = requireDialog() as AlertDialog + + override fun onCreateDialog(savedInstanceState: Bundle?) = + AlertDialog.Builder(requireContext(), R.style.AlertDialogTheme) + .setTitle(R.string.euicc_memory_reset_title) + .setMessage(getString(R.string.euicc_memory_reset_message, eid, confirmText)) + .setView(editText) + // Set listener to null to prevent auto closing + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(R.string.euicc_memory_reset_invoke_button, null) + .create() + + override fun onResume() { + super.onResume() + alertDialog.setCanceledOnTouchOutside(false) + alertDialog.getButton(AlertDialog.BUTTON_POSITIVE) + .setOnClickListener { if (!confirmed) confirmation() } + alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE) + .setOnClickListener { if (!confirmed) dismiss() } + } + + private fun confirmation() { + toast?.cancel() + if (!isMatched) { + Log.d(TAG, buildString { + appendLine("User input is mismatch:") + appendLine(editText.text) + appendLine(confirmText) + }) + val resId = R.string.toast_euicc_memory_reset_confirm_text_mismatched + toast = Toast.makeText(requireContext(), resId, Toast.LENGTH_LONG) + return + } + confirmed = true + preventUserAction() + + requireParentFragment().lifecycleScope.launch { + ensureEuiccChannelManager() + euiccChannelManagerService.waitForForegroundTask() + + euiccChannelManagerService.launchMemoryReset(slotId, portId) + .onStart { + parentFragment?.notifyEuiccProfilesChanged() + + val resId = R.string.toast_euicc_memory_reset_finitshed + toast = Toast.makeText(requireContext(), resId, Toast.LENGTH_LONG) + + runCatching(::dismiss) + } + .waitDone() + } + } + + private fun preventUserAction() { + editText.isEnabled = false + alertDialog.setCancelable(false) + alertDialog.setCanceledOnTouchOutside(false) + alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false + alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).isEnabled = false + } +} 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 c54d6a1..cdb58f1 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 @@ -1,6 +1,7 @@ package im.angry.openeuicc.ui import android.content.Intent +import android.content.pm.PackageManager import android.net.Uri import android.os.Build import android.os.Bundle @@ -77,6 +78,11 @@ open class SettingsFragment: PreferenceFragmentCompat() { requirePreference("pref_developer_ignore_tls_certificate") .bindBooleanFlow(preferenceRepository.ignoreTLSCertificateFlow) + + requirePreference("pref_developer_euicc_memory_reset").apply { + isVisible = context.packageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST) + bindBooleanFlow(preferenceRepository.euiccMemoryResetFlow) + } } protected fun requirePreference(key: CharSequence) = 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 3c7bbf4..34d1cfd 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 @@ -33,6 +33,7 @@ internal object PreferenceKeys { val DEVELOPER_OPTIONS_ENABLED = booleanPreferencesKey("developer_options_enabled") val UNFILTERED_PROFILE_LIST = booleanPreferencesKey("unfiltered_profile_list") val IGNORE_TLS_CERTIFICATE = booleanPreferencesKey("ignore_tls_certificate") + val EUICC_MEMORY_RESET = booleanPreferencesKey("euicc_memory_reset") } open class PreferenceRepository(private val context: Context) { @@ -50,6 +51,7 @@ open class PreferenceRepository(private val context: Context) { val developerOptionsEnabledFlow = bindFlow(PreferenceKeys.DEVELOPER_OPTIONS_ENABLED, false) val unfilteredProfileListFlow = bindFlow(PreferenceKeys.UNFILTERED_PROFILE_LIST, false) val ignoreTLSCertificateFlow = bindFlow(PreferenceKeys.IGNORE_TLS_CERTIFICATE, false) + val euiccMemoryResetFlow = bindFlow(PreferenceKeys.EUICC_MEMORY_RESET, false) protected fun bindFlow(key: Preferences.Key, defaultValue: T): PreferenceFlowWrapper = PreferenceFlowWrapper(context, key, defaultValue) diff --git a/app-common/src/main/res/drawable/ic_euicc_memory_reset.xml b/app-common/src/main/res/drawable/ic_euicc_memory_reset.xml new file mode 100644 index 0000000..f1ca8c1 --- /dev/null +++ b/app-common/src/main/res/drawable/ic_euicc_memory_reset.xml @@ -0,0 +1,18 @@ + + + + diff --git a/app-common/src/main/res/menu/fragment_euicc.xml b/app-common/src/main/res/menu/fragment_euicc.xml index b54eaf1..6e2dfbe 100644 --- a/app-common/src/main/res/menu/fragment_euicc.xml +++ b/app-common/src/main/res/menu/fragment_euicc.xml @@ -10,4 +10,9 @@ android:id="@+id/euicc_info" android:title="@string/euicc_info" app:showAsAction="never" /> + + \ No newline at end of file diff --git a/app-common/src/main/res/values-ja/strings.xml b/app-common/src/main/res/values-ja/strings.xml index 22b8a4c..1fd361c 100644 --- a/app-common/src/main/res/values-ja/strings.xml +++ b/app-common/src/main/res/values-ja/strings.xml @@ -150,4 +150,16 @@ 情報 アプリバージョン ソースコード + 確認文字列が一致しません + このチップは消去されました + eSIM チップを消去しています + eSIM チップの消去は失敗しました + eSIM を消去する + eSIM を消去する + このチップ内のすべてのプロファイルを削除することをご確認してください。この操作は元に戻せないことをご理解してください。\n\nEID: %s\n\n%s + 確認のため、ここに「%s」を入力してください + EID が %s で終わるチップを消去することに同意します。これは元に戻せないことを理解しています。 + 消去する + eUICC の消去を可能にする + この操作は、デフォルトでは非表示になっている危険な操作です。代わりに、すべての構成ファイルを手動で削除することもできます。 diff --git a/app-common/src/main/res/values-zh-rCN/strings.xml b/app-common/src/main/res/values-zh-rCN/strings.xml index e255d9a..8c36091 100644 --- a/app-common/src/main/res/values-zh-rCN/strings.xml +++ b/app-common/src/main/res/values-zh-rCN/strings.xml @@ -150,4 +150,16 @@ 无视 SM-DP+ 的 TLS 证书 允许 RSP 服务器使用任意证书 无信息 + 输入的确认文本不匹配 + 此芯片已被擦除 + 正在擦除 eSIM 芯片 + eSIM 芯片擦除失败 + 擦除 eSIM 芯片 + 擦除 eSIM 芯片 + 请确认删除此芯片上的所有配置文件,并了解此操作不可逆。\n\nEID: %s\n\n%s + 请在此处输入「%s」以确认 + 我确认擦除 EID 以 %s 结尾的芯片,并了解此操作不可逆 + 擦除 + 允许擦除 eUICC + 此操作是默认隐藏的危险操作。作为替代方案,您可以手动删除所有配置文件。 \ No newline at end of file diff --git a/app-common/src/main/res/values-zh-rTW/strings.xml b/app-common/src/main/res/values-zh-rTW/strings.xml index b8c94b5..3d8270d 100644 --- a/app-common/src/main/res/values-zh-rTW/strings.xml +++ b/app-common/src/main/res/values-zh-rTW/strings.xml @@ -150,4 +150,16 @@ 忽略 SM-DP+ 的 TLS 證書 允許 RSP 伺服器使用任意證書 無資訊 + 輸入的確認文字不匹配 + 此晶片已被擦除 + 正在擦除 eSIM 晶片 + eSIM 晶片擦除失敗 + 擦除 eSIM 晶片 + 擦除 eSIM 晶片 + 請確認刪除此晶片上的所有配置文件,並了解此操作不可逆。\n\nEID: %s\n\n%s + 請在此輸入「%s」以確認 + 我確認擦除 EID 以 %s 結尾的晶片,並了解此操作不可逆 + 擦除 + 允許擦除 eUICC + 此操作是預設隱藏的危險操作。作為替代方案,您可以手動刪除所有設定檔。 \ 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 8a0ac24..cc84381 100644 --- a/app-common/src/main/res/values/strings.xml +++ b/app-common/src/main/res/values/strings.xml @@ -30,6 +30,8 @@ Cannot switch to new eSIM profile. Confirmation string mismatch + Confirmation string mismatch + This chip has been erased ICCID copied to clipboard Serial number copied to clipboard EID copied to clipboard @@ -48,6 +50,8 @@ Failed to delete eSIM profile Switching eSIM profile Failed to switch eSIM profile + Erasing eSIM chip + Failed to erase eSIM chip New eSIM Server (RSP / SM-DP+) @@ -142,6 +146,13 @@ Unknown eSIM CI Answer To Reset (ATR) + Erase eUICC + Erase eUICC + Please confirm to delete all profiles on this chip and understand that this operation is irreversible.\n\nEID: %s\n\n%s + Type \'%s\' here to confirm + I CONFIRM TO ERASE THE CHIP WHOSE EID ENDS WITH %s AND UNDERSTAND THAT THIS IS IRREVERSIBLE + Erase + Yes No @@ -174,6 +185,8 @@ Include non-production profiles in the list Ignore SM-DP+ TLS certificate Accept any TLS certificate used by the RSP server + Allow erasing eUICC + This is a dangerous operation and hidden by default. As an alternative, you can delete all profiles manually. 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 944bd27..008f9ae 100644 --- a/app-common/src/main/res/xml/pref_settings.xml +++ b/app-common/src/main/res/xml/pref_settings.xml @@ -69,6 +69,13 @@ app:summary="@string/pref_developer_ignore_tls_certificate_desc" app:title="@string/pref_developer_ignore_tls_certificate" /> + + Date: Sat, 8 Mar 2025 20:28:19 -0500 Subject: [PATCH 12/14] fix: Don't use beginTrackedOperation for erasure. It's wrong. --- .../angry/openeuicc/service/EuiccChannelManagerService.kt | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/app-common/src/main/java/im/angry/openeuicc/service/EuiccChannelManagerService.kt b/app-common/src/main/java/im/angry/openeuicc/service/EuiccChannelManagerService.kt index 52943d8..9957f30 100644 --- a/app-common/src/main/java/im/angry/openeuicc/service/EuiccChannelManagerService.kt +++ b/app-common/src/main/java/im/angry/openeuicc/service/EuiccChannelManagerService.kt @@ -502,12 +502,8 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker { getString(R.string.task_euicc_memory_reset_failure), R.drawable.ic_euicc_memory_reset ) { - euiccChannelManager.beginTrackedOperation(slotId, portId) { - euiccChannelManager.withEuiccChannel(slotId, portId) { channel -> - channel.lpa.euiccMemoryReset() - } - - preferenceRepository.euiccMemoryResetFlow.first() + euiccChannelManager.withEuiccChannel(slotId, portId) { channel -> + channel.lpa.euiccMemoryReset() } } } \ No newline at end of file From f6c50490b86bc912e1cb52ca9f08e49138adeef0 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sat, 8 Mar 2025 20:35:00 -0500 Subject: [PATCH 13/14] fix: No USB_HOST feature requirement for eUICC erasure --- .../main/java/im/angry/openeuicc/ui/SettingsFragment.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 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 cdb58f1..b085286 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 @@ -1,7 +1,6 @@ package im.angry.openeuicc.ui import android.content.Intent -import android.content.pm.PackageManager import android.net.Uri import android.os.Build import android.os.Bundle @@ -79,10 +78,8 @@ open class SettingsFragment: PreferenceFragmentCompat() { requirePreference("pref_developer_ignore_tls_certificate") .bindBooleanFlow(preferenceRepository.ignoreTLSCertificateFlow) - requirePreference("pref_developer_euicc_memory_reset").apply { - isVisible = context.packageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST) - bindBooleanFlow(preferenceRepository.euiccMemoryResetFlow) - } + requirePreference("pref_developer_euicc_memory_reset") + .bindBooleanFlow(preferenceRepository.euiccMemoryResetFlow) } protected fun requirePreference(key: CharSequence) = From 29d72e8db07a7e0a4b4b74cfbf4904827d477944 Mon Sep 17 00:00:00 2001 From: septs Date: Sun, 9 Mar 2025 09:58:21 +0800 Subject: [PATCH 14/14] feat: add refresh after switch preference to developer settings --- .../java/im/angry/openeuicc/ui/SettingsFragment.kt | 3 +++ app-common/src/main/res/xml/pref_settings.xml | 8 ++++++-- .../angry/openeuicc/ui/UnprivilegedSettingsFragment.kt | 4 ---- .../src/main/res/xml/pref_unprivileged_settings.xml | 10 ---------- 4 files changed, 9 insertions(+), 16 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 b085286..d137e90 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 @@ -78,6 +78,9 @@ open class SettingsFragment: PreferenceFragmentCompat() { requirePreference("pref_developer_ignore_tls_certificate") .bindBooleanFlow(preferenceRepository.ignoreTLSCertificateFlow) + requirePreference("pref_developer_refresh_after_switch") + .bindBooleanFlow(preferenceRepository.refreshAfterSwitchFlow) + requirePreference("pref_developer_euicc_memory_reset") .bindBooleanFlow(preferenceRepository.euiccMemoryResetFlow) } diff --git a/app-common/src/main/res/xml/pref_settings.xml b/app-common/src/main/res/xml/pref_settings.xml index e639e6f..f719ef5 100644 --- a/app-common/src/main/res/xml/pref_settings.xml +++ b/app-common/src/main/res/xml/pref_settings.xml @@ -57,17 +57,21 @@ app:title="@string/pref_developer" app:iconSpaceReserved="false"> + + diff --git a/app-unpriv/src/main/java/im/angry/openeuicc/ui/UnprivilegedSettingsFragment.kt b/app-unpriv/src/main/java/im/angry/openeuicc/ui/UnprivilegedSettingsFragment.kt index 31c9a77..7d7e8f5 100644 --- a/app-unpriv/src/main/java/im/angry/openeuicc/ui/UnprivilegedSettingsFragment.kt +++ b/app-unpriv/src/main/java/im/angry/openeuicc/ui/UnprivilegedSettingsFragment.kt @@ -31,12 +31,8 @@ class UnprivilegedSettingsFragment : SettingsFragment() { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { super.onCreatePreferences(savedInstanceState, rootKey) addPreferencesFromResource(R.xml.pref_unprivileged_settings) - mergePreferenceOverlay("pref_developer_overlay", "pref_developer") mergePreferenceOverlay("pref_info_overlay", "pref_info") - requirePreference("pref_developer_refreshed_after_switch") - .bindBooleanFlow(preferenceRepository.refreshAfterSwitchFlow) - requirePreference("pref_info_ara_m").apply { summary = firstSigner.encodeHex() setOnPreferenceClickListener { diff --git a/app-unpriv/src/main/res/xml/pref_unprivileged_settings.xml b/app-unpriv/src/main/res/xml/pref_unprivileged_settings.xml index df5bfee..3281caf 100644 --- a/app-unpriv/src/main/res/xml/pref_unprivileged_settings.xml +++ b/app-unpriv/src/main/res/xml/pref_unprivileged_settings.xml @@ -1,15 +1,5 @@ - - -