From f4a3b241d0b119c204d7ee52163ab5125dcfd112 Mon Sep 17 00:00:00 2001 From: septs Date: Thu, 12 Dec 2024 17:36:36 +0800 Subject: [PATCH 01/14] feat: profile rename strong checking --- .../openeuicc/ui/ProfileRenameFragment.kt | 59 ++++++++++++++----- app-common/src/main/res/values/strings.xml | 2 + 2 files changed, 46 insertions(+), 15 deletions(-) diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt index 8582278..18b28ba 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt @@ -5,34 +5,49 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.EditText import android.widget.ProgressBar import android.widget.Toast import androidx.appcompat.widget.Toolbar +import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import com.google.android.material.textfield.TextInputLayout import im.angry.openeuicc.common.R import im.angry.openeuicc.service.EuiccChannelManagerService.Companion.waitDone import im.angry.openeuicc.util.* import kotlinx.coroutines.launch +import java.nio.charset.Charset class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragmentMarker { companion object { const val TAG = "ProfileRenameFragment" + const val FIELD_ICCID = "iccid" + const val FIELD_CURRENT_NAME = "currentName" fun newInstance(slotId: Int, portId: Int, iccid: String, currentName: String): ProfileRenameFragment { val instance = newInstanceEuicc(ProfileRenameFragment::class.java, slotId, portId) instance.requireArguments().apply { - putString("iccid", iccid) - putString("currentName", currentName) + putString(FIELD_ICCID, iccid) + putString(FIELD_CURRENT_NAME, currentName) } return instance } } private lateinit var toolbar: Toolbar - private lateinit var profileRenameNewName: TextInputLayout + private lateinit var editText: EditText private lateinit var progress: ProgressBar + private val utf8Charset = Charset.forName("UTF-8") + + private var toast: Toast? = null + + private val iccid: String + get() = requireArguments().getString(FIELD_ICCID)!! + + private val currentName: String + get() = requireArguments().getString(FIELD_CURRENT_NAME)!! + private var renaming = false override fun onCreateView( @@ -43,7 +58,9 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment val view = inflater.inflate(R.layout.fragment_profile_rename, container, false) toolbar = view.requireViewById(R.id.toolbar) - profileRenameNewName = view.requireViewById(R.id.profile_rename_new_name) + editText = view.requireViewById(R.id.profile_rename_new_name).let { + it.editText!! + } progress = view.requireViewById(R.id.progress) toolbar.inflateMenu(R.menu.fragment_profile_rename) @@ -67,7 +84,7 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment override fun onStart() { super.onStart() - profileRenameNewName.editText!!.setText(requireArguments().getString("currentName")) + editText.setText(currentName) } override fun onResume() { @@ -82,25 +99,37 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment } private fun rename() { - val name = profileRenameNewName.editText!!.text.toString().trim() - if (name.length >= 64) { - Toast.makeText(context, R.string.toast_profile_name_too_long, Toast.LENGTH_LONG).show() + val name = editText.text.toString().trim() + // SGP.22 v2.2.2 (Page 205 of 268) + // https://www.gsma.com/solutions-and-impact/technologies/esim/wp-content/uploads/2020/06/SGP.22-v2.2.2.pdf + // ASN.1 definition is `profileNickname [16] UTF8String (SIZE(0..64))` + // code points <= 64 or encoded bytes <= 64? + toast?.cancel() + val toastResId = try { + utf8Charset.encode(name) // detect is utf8 valid + when { + name == currentName -> R.string.toast_profile_name_not_changed + name.length >= 64 -> R.string.toast_profile_name_too_long + else -> null + } + } catch (e: CharacterCodingException) { + R.string.toast_profile_name_encode_failed + } + if (toastResId != null) { + toast = Toast.makeText(requireContext(), toastResId, Toast.LENGTH_LONG) + toast!!.show() return } renaming = true progress.isIndeterminate = true - progress.visibility = View.VISIBLE + progress.isVisible = true lifecycleScope.launch { ensureEuiccChannelManager() euiccChannelManagerService.waitForForegroundTask() - euiccChannelManagerService.launchProfileRenameTask( - slotId, - portId, - requireArguments().getString("iccid")!!, - name - ).waitDone() + euiccChannelManagerService.launchProfileRenameTask(slotId, portId, iccid, name) + .waitDone() if (parentFragment is EuiccProfilesChangedListener) { (parentFragment as EuiccProfilesChangedListener).onEuiccProfilesChanged() diff --git a/app-common/src/main/res/values/strings.xml b/app-common/src/main/res/values/strings.xml index f308826..738e484 100644 --- a/app-common/src/main/res/values/strings.xml +++ b/app-common/src/main/res/values/strings.xml @@ -28,7 +28,9 @@ The operation was successful, but your phone\'s modem refused to refresh. You might need to toggle airplane mode or reboot in order to use the new profile. Cannot switch to new eSIM profile. + Nickname has not changed Nickname cannot be longer than 64 characters + The input string could not be encoded ICCID copied to clipboard Select Slot -- 2.45.3 From 335d51ec53a1178632c74a9795ca535726f11b74 Mon Sep 17 00:00:00 2001 From: septs Date: Thu, 12 Dec 2024 19:31:20 +0800 Subject: [PATCH 02/14] refactor: asserts --- .../openeuicc/ui/ProfileRenameFragment.kt | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt index 18b28ba..d11c757 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt @@ -98,22 +98,28 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment } } - private fun rename() { - val name = editText.text.toString().trim() + private fun assertInputNameAsToastResId(): Pair { // SGP.22 v2.2.2 (Page 205 of 268) // https://www.gsma.com/solutions-and-impact/technologies/esim/wp-content/uploads/2020/06/SGP.22-v2.2.2.pdf // ASN.1 definition is `profileNickname [16] UTF8String (SIZE(0..64))` // code points <= 64 or encoded bytes <= 64? + val name = editText.text.toString().trim() + if (name.length > 64) { + return Pair(R.string.toast_profile_name_too_long, false) + } else if (name == currentName) { + return Pair(R.string.toast_profile_name_not_changed, true) + } else if (runCatching { utf8Charset.encode(name) }.isFailure) { + return Pair(R.string.toast_profile_name_encode_failed, false) + } + return Pair(null, false) + } + + private fun rename() { toast?.cancel() - val toastResId = try { - utf8Charset.encode(name) // detect is utf8 valid - when { - name == currentName -> R.string.toast_profile_name_not_changed - name.length >= 64 -> R.string.toast_profile_name_too_long - else -> null - } - } catch (e: CharacterCodingException) { - R.string.toast_profile_name_encode_failed + val name = editText.text.toString().trim() + val (toastResId, needDismiss) = assertInputNameAsToastResId() + if (needDismiss) { + dismiss() } if (toastResId != null) { toast = Toast.makeText(requireContext(), toastResId, Toast.LENGTH_LONG) -- 2.45.3 From e123a660e968a55b2f27a67bf83bd4b7f59e837d Mon Sep 17 00:00:00 2001 From: septs Date: Thu, 12 Dec 2024 19:56:40 +0800 Subject: [PATCH 03/14] refactor: asserts --- .../openeuicc/ui/ProfileRenameFragment.kt | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt index d11c757..df3dfd0 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt @@ -98,32 +98,28 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment } } - private fun assertInputNameAsToastResId(): Pair { + private fun assertInputNameAsToastResId(name: String): Int? { // SGP.22 v2.2.2 (Page 205 of 268) // https://www.gsma.com/solutions-and-impact/technologies/esim/wp-content/uploads/2020/06/SGP.22-v2.2.2.pdf // ASN.1 definition is `profileNickname [16] UTF8String (SIZE(0..64))` // code points <= 64 or encoded bytes <= 64? - val name = editText.text.toString().trim() if (name.length > 64) { - return Pair(R.string.toast_profile_name_too_long, false) + return R.string.toast_profile_name_too_long } else if (name == currentName) { - return Pair(R.string.toast_profile_name_not_changed, true) + return R.string.toast_profile_name_not_changed } else if (runCatching { utf8Charset.encode(name) }.isFailure) { - return Pair(R.string.toast_profile_name_encode_failed, false) + return R.string.toast_profile_name_encode_failed } - return Pair(null, false) + return null } private fun rename() { toast?.cancel() val name = editText.text.toString().trim() - val (toastResId, needDismiss) = assertInputNameAsToastResId() - if (needDismiss) { - dismiss() - } - if (toastResId != null) { - toast = Toast.makeText(requireContext(), toastResId, Toast.LENGTH_LONG) + assertInputNameAsToastResId(name)?.let { resId -> + toast = Toast.makeText(requireContext(), resId, Toast.LENGTH_LONG) toast!!.show() + if (resId == R.string.toast_profile_name_not_changed) dismiss() return } -- 2.45.3 From a048673f4970541acd4695ad061995dc223bfce5 Mon Sep 17 00:00:00 2001 From: septs Date: Thu, 12 Dec 2024 20:11:06 +0800 Subject: [PATCH 04/14] chore: simplify encode utf-8 --- .../main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt index df3dfd0..207603e 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt @@ -38,8 +38,6 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment private lateinit var editText: EditText private lateinit var progress: ProgressBar - private val utf8Charset = Charset.forName("UTF-8") - private var toast: Toast? = null private val iccid: String @@ -107,7 +105,7 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment return R.string.toast_profile_name_too_long } else if (name == currentName) { return R.string.toast_profile_name_not_changed - } else if (runCatching { utf8Charset.encode(name) }.isFailure) { + } else if (runCatching { name.toByteArray(Charsets.UTF_8) }.isFailure) { return R.string.toast_profile_name_encode_failed } return null -- 2.45.3 From faab418e08c0366b9cc8e0d59c25b4d8433a19f2 Mon Sep 17 00:00:00 2001 From: septs Date: Thu, 12 Dec 2024 20:31:35 +0800 Subject: [PATCH 05/14] chore: simplify encode utf-8 --- .../angry/openeuicc/ui/ProfileRenameFragment.kt | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt index 207603e..6ea2e17 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt @@ -16,7 +16,6 @@ import im.angry.openeuicc.common.R import im.angry.openeuicc.service.EuiccChannelManagerService.Companion.waitDone import im.angry.openeuicc.util.* import kotlinx.coroutines.launch -import java.nio.charset.Charset class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragmentMarker { companion object { @@ -101,14 +100,14 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment // https://www.gsma.com/solutions-and-impact/technologies/esim/wp-content/uploads/2020/06/SGP.22-v2.2.2.pdf // ASN.1 definition is `profileNickname [16] UTF8String (SIZE(0..64))` // code points <= 64 or encoded bytes <= 64? - if (name.length > 64) { - return R.string.toast_profile_name_too_long - } else if (name == currentName) { - return R.string.toast_profile_name_not_changed - } else if (runCatching { name.toByteArray(Charsets.UTF_8) }.isFailure) { - return R.string.toast_profile_name_encode_failed + if (name == currentName) return R.string.toast_profile_name_not_changed + return try { + val length = name.toByteArray(Charsets.UTF_8).size + if (length <= 64) return null + R.string.toast_profile_name_too_long + } catch (e: CharacterCodingException) { + R.string.toast_profile_name_encode_failed } - return null } private fun rename() { -- 2.45.3 From c2eb833631b6adb4558d9d8e8d1fe4ade0036c9f Mon Sep 17 00:00:00 2001 From: septs Date: Fri, 13 Dec 2024 10:53:59 +0800 Subject: [PATCH 06/14] feat: more toast hints --- .../openeuicc/ui/ProfileRenameFragment.kt | 49 ++++++++++++------- app-common/src/main/res/values/strings.xml | 2 + 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt index 6ea2e17..08005dd 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt @@ -38,6 +38,11 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment private lateinit var progress: ProgressBar private var toast: Toast? = null + set(toast) { + field?.cancel() + field = toast + field?.show() + } private val iccid: String get() = requireArguments().getString(FIELD_ICCID)!! @@ -45,6 +50,9 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment private val currentName: String get() = requireArguments().getString(FIELD_CURRENT_NAME)!! + private val editedName: String + get() = editText.text.toString().trim() + private var renaming = false override fun onCreateView( @@ -95,30 +103,35 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment } } - private fun assertInputNameAsToastResId(name: String): Int? { + private fun rename() { // SGP.22 v2.2.2 (Page 205 of 268) // https://www.gsma.com/solutions-and-impact/technologies/esim/wp-content/uploads/2020/06/SGP.22-v2.2.2.pdf // ASN.1 definition is `profileNickname [16] UTF8String (SIZE(0..64))` // code points <= 64 or encoded bytes <= 64? - if (name == currentName) return R.string.toast_profile_name_not_changed - return try { - val length = name.toByteArray(Charsets.UTF_8).size - if (length <= 64) return null - R.string.toast_profile_name_too_long + try { + val length = editedName.toByteArray(Charsets.UTF_8).size + if (length > 64) { + toast = Toast.makeText( + requireContext(), + R.string.toast_profile_name_too_long, + Toast.LENGTH_LONG + ) + return + } } catch (e: CharacterCodingException) { - R.string.toast_profile_name_encode_failed - } - } - - private fun rename() { - toast?.cancel() - val name = editText.text.toString().trim() - assertInputNameAsToastResId(name)?.let { resId -> - toast = Toast.makeText(requireContext(), resId, Toast.LENGTH_LONG) - toast!!.show() - if (resId == R.string.toast_profile_name_not_changed) dismiss() + toast = Toast.makeText( + requireContext(), + R.string.toast_profile_name_encode_failed, + Toast.LENGTH_LONG + ) return } + val toastMessage = when { + editedName.isEmpty() -> getString(R.string.toast_profile_name_restore_defaults) + editedName == currentName -> getString(R.string.toast_profile_name_not_changed) + else -> getString(R.string.toast_profile_name_changed, currentName, editedName) + } + toast = Toast.makeText(requireContext(), toastMessage, Toast.LENGTH_LONG) renaming = true progress.isIndeterminate = true @@ -127,7 +140,7 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment lifecycleScope.launch { ensureEuiccChannelManager() euiccChannelManagerService.waitForForegroundTask() - euiccChannelManagerService.launchProfileRenameTask(slotId, portId, iccid, name) + euiccChannelManagerService.launchProfileRenameTask(slotId, portId, iccid, editedName) .waitDone() if (parentFragment is EuiccProfilesChangedListener) { diff --git a/app-common/src/main/res/values/strings.xml b/app-common/src/main/res/values/strings.xml index 738e484..d5f7a5c 100644 --- a/app-common/src/main/res/values/strings.xml +++ b/app-common/src/main/res/values/strings.xml @@ -28,9 +28,11 @@ The operation was successful, but your phone\'s modem refused to refresh. You might need to toggle airplane mode or reboot in order to use the new profile. Cannot switch to new eSIM profile. + The profile name will be updated from \'%s\' to \'%s\' Nickname has not changed Nickname cannot be longer than 64 characters The input string could not be encoded + This profile name will be restored to the default ICCID copied to clipboard Select Slot -- 2.45.3 From 4a4ca973e8fcf49005a019d1a04460aaa8e4ffae Mon Sep 17 00:00:00 2001 From: septs Date: Fri, 13 Dec 2024 11:22:36 +0800 Subject: [PATCH 07/14] chore: simplify toast --- .../openeuicc/ui/ProfileRenameFragment.kt | 40 ++++++++++--------- .../res/layout/fragment_profile_rename.xml | 3 +- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt index 08005dd..c7b52a2 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt @@ -19,6 +19,8 @@ import kotlinx.coroutines.launch class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragmentMarker { companion object { + val SPACE_PATTERN = Regex("\\s", RegexOption.MULTILINE) + const val TAG = "ProfileRenameFragment" const val FIELD_ICCID = "iccid" const val FIELD_CURRENT_NAME = "currentName" @@ -38,11 +40,6 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment private lateinit var progress: ProgressBar private var toast: Toast? = null - set(toast) { - field?.cancel() - field = toast - field?.show() - } private val iccid: String get() = requireArguments().getString(FIELD_ICCID)!! @@ -50,9 +47,6 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment private val currentName: String get() = requireArguments().getString(FIELD_CURRENT_NAME)!! - private val editedName: String - get() = editText.text.toString().trim() - private var renaming = false override fun onCreateView( @@ -104,11 +98,14 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment } private fun rename() { - // SGP.22 v2.2.2 (Page 205 of 268) - // https://www.gsma.com/solutions-and-impact/technologies/esim/wp-content/uploads/2020/06/SGP.22-v2.2.2.pdf - // ASN.1 definition is `profileNickname [16] UTF8String (SIZE(0..64))` - // code points <= 64 or encoded bytes <= 64? + toast?.cancel() + val editedName = editText.text.toString().trim() + .replace(SPACE_PATTERN, " ") try { + // SGP.22 v2.2.2 (Page 205 of 268) + // https://www.gsma.com/solutions-and-impact/technologies/esim/wp-content/uploads/2020/06/SGP.22-v2.2.2.pdf + // ASN.1 definition is `profileNickname [16] UTF8String (SIZE(0..64))` + // code points <= 64 or encoded bytes <= 64? val length = editedName.toByteArray(Charsets.UTF_8).size if (length > 64) { toast = Toast.makeText( @@ -116,22 +113,27 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment R.string.toast_profile_name_too_long, Toast.LENGTH_LONG ) + toast!!.show() return } - } catch (e: CharacterCodingException) { + } catch (_: CharacterCodingException) { + // invalid UTF-8 sequence toast = Toast.makeText( requireContext(), R.string.toast_profile_name_encode_failed, Toast.LENGTH_LONG ) + toast!!.show() return + } finally { + val message = when { + editedName.isEmpty() -> getString(R.string.toast_profile_name_restore_defaults) + editedName == currentName -> getString(R.string.toast_profile_name_not_changed) + else -> getString(R.string.toast_profile_name_changed, currentName, editedName) + } + toast = Toast.makeText(requireContext(), message, Toast.LENGTH_LONG) + toast!!.show() } - val toastMessage = when { - editedName.isEmpty() -> getString(R.string.toast_profile_name_restore_defaults) - editedName == currentName -> getString(R.string.toast_profile_name_not_changed) - else -> getString(R.string.toast_profile_name_changed, currentName, editedName) - } - toast = Toast.makeText(requireContext(), toastMessage, Toast.LENGTH_LONG) renaming = true progress.isIndeterminate = true diff --git a/app-common/src/main/res/layout/fragment_profile_rename.xml b/app-common/src/main/res/layout/fragment_profile_rename.xml index f0da20d..32c0b78 100644 --- a/app-common/src/main/res/layout/fragment_profile_rename.xml +++ b/app-common/src/main/res/layout/fragment_profile_rename.xml @@ -48,7 +48,8 @@ + android:layout_height="match_parent" + android:singleLine="true" /> -- 2.45.3 From bcb20ebf0f0b49c4f03357fda51ba993c28c1df2 Mon Sep 17 00:00:00 2001 From: septs Date: Fri, 13 Dec 2024 11:41:06 +0800 Subject: [PATCH 08/14] chore: simplify toast --- .../openeuicc/ui/ProfileRenameFragment.kt | 43 ++++++++----------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt index c7b52a2..897bdd7 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt @@ -100,39 +100,30 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment private fun rename() { toast?.cancel() val editedName = editText.text.toString().trim() - .replace(SPACE_PATTERN, " ") - try { - // SGP.22 v2.2.2 (Page 205 of 268) - // https://www.gsma.com/solutions-and-impact/technologies/esim/wp-content/uploads/2020/06/SGP.22-v2.2.2.pdf - // ASN.1 definition is `profileNickname [16] UTF8String (SIZE(0..64))` - // code points <= 64 or encoded bytes <= 64? - val length = editedName.toByteArray(Charsets.UTF_8).size - if (length > 64) { - toast = Toast.makeText( - requireContext(), - R.string.toast_profile_name_too_long, - Toast.LENGTH_LONG - ) - toast!!.show() - return - } - } catch (_: CharacterCodingException) { - // invalid UTF-8 sequence - toast = Toast.makeText( - requireContext(), - R.string.toast_profile_name_encode_failed, - Toast.LENGTH_LONG - ) - toast!!.show() - return - } finally { + // replace \s as space (inc. new lien and spaces) + .replace(SPACE_PATTERN, "\u0020") + // SGP.22 v2.2.2 (Page 205 of 268) + // https://www.gsma.com/solutions-and-impact/technologies/esim/wp-content/uploads/2020/06/SGP.22-v2.2.2.pdf + // ASN.1 definition is `profileNickname [16] UTF8String (SIZE(0..64))` + // code points <= 64 or encoded bytes <= 64? + runCatching { editedName.toByteArray(Charsets.UTF_8).size }.let { result -> + var kept = false val message = when { + result.isFailure -> { + kept = true // invalid UTF-8 sequence + getString(R.string.toast_profile_name_encode_failed) + } + result.getOrNull()!! > 64 -> { + kept = true // exceeds 64 bytes + getString(R.string.toast_profile_name_too_long) + } editedName.isEmpty() -> getString(R.string.toast_profile_name_restore_defaults) editedName == currentName -> getString(R.string.toast_profile_name_not_changed) else -> getString(R.string.toast_profile_name_changed, currentName, editedName) } toast = Toast.makeText(requireContext(), message, Toast.LENGTH_LONG) toast!!.show() + if (kept) return } renaming = true -- 2.45.3 From 141cc6845ece72c22295e4fafe25e359636022d2 Mon Sep 17 00:00:00 2001 From: septs Date: Fri, 13 Dec 2024 11:44:32 +0800 Subject: [PATCH 09/14] fix: typo --- .../main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt index 897bdd7..28962c8 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt @@ -100,7 +100,7 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment private fun rename() { toast?.cancel() val editedName = editText.text.toString().trim() - // replace \s as space (inc. new lien and spaces) + // replace \s as space (inc. new line and spaces) .replace(SPACE_PATTERN, "\u0020") // SGP.22 v2.2.2 (Page 205 of 268) // https://www.gsma.com/solutions-and-impact/technologies/esim/wp-content/uploads/2020/06/SGP.22-v2.2.2.pdf -- 2.45.3 From 6010a64e7cd0b9b554c7312223b7a7cb0dbbd220 Mon Sep 17 00:00:00 2001 From: septs Date: Fri, 13 Dec 2024 13:13:41 +0800 Subject: [PATCH 10/14] chore: accept reviews --- .../service/EuiccChannelManagerService.kt | 5 +- .../openeuicc/ui/ProfileRenameFragment.kt | 50 +++++++++---------- app-common/src/main/res/values/strings.xml | 6 +-- .../lpac_jni/LocalProfileAssistant.kt | 12 +++-- .../impl/LocalProfileAssistantImpl.kt | 20 +++++++- 5 files changed, 56 insertions(+), 37 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 c4d16df..72ad6dc 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 @@ -415,10 +415,7 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker { R.drawable.ic_task_rename ) { val res = euiccChannelManager.withEuiccChannel(slotId, portId) { channel -> - channel.lpa.setNickname( - iccid, - name - ) + channel.lpa.setNickname(iccid, name) } if (!res) { diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt index 28962c8..d96bf0f 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt @@ -16,6 +16,8 @@ import im.angry.openeuicc.common.R import im.angry.openeuicc.service.EuiccChannelManagerService.Companion.waitDone import im.angry.openeuicc.util.* import kotlinx.coroutines.launch +import net.typeblog.lpac_jni.LocalProfileAssistant.ProfileNicknameException as NicknameException +import net.typeblog.lpac_jni.LocalProfileAssistant.ProfileNicknameException.Kind as SetFailedKind class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragmentMarker { companion object { @@ -98,32 +100,17 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment } private fun rename() { - toast?.cancel() val editedName = editText.text.toString().trim() // replace \s as space (inc. new line and spaces) .replace(SPACE_PATTERN, "\u0020") - // SGP.22 v2.2.2 (Page 205 of 268) - // https://www.gsma.com/solutions-and-impact/technologies/esim/wp-content/uploads/2020/06/SGP.22-v2.2.2.pdf - // ASN.1 definition is `profileNickname [16] UTF8String (SIZE(0..64))` - // code points <= 64 or encoded bytes <= 64? - runCatching { editedName.toByteArray(Charsets.UTF_8).size }.let { result -> - var kept = false - val message = when { - result.isFailure -> { - kept = true // invalid UTF-8 sequence - getString(R.string.toast_profile_name_encode_failed) - } - result.getOrNull()!! > 64 -> { - kept = true // exceeds 64 bytes - getString(R.string.toast_profile_name_too_long) - } - editedName.isEmpty() -> getString(R.string.toast_profile_name_restore_defaults) - editedName == currentName -> getString(R.string.toast_profile_name_not_changed) - else -> getString(R.string.toast_profile_name_changed, currentName, editedName) - } - toast = Toast.makeText(requireContext(), message, Toast.LENGTH_LONG) - toast!!.show() - if (kept) return + val toastMessage = when { + editedName.isEmpty() -> getString(R.string.toast_profile_name_restore_defaults) + editedName == currentName -> getString(R.string.toast_profile_name_is_unchanged) + else -> getString(R.string.toast_profile_name_changed, currentName, editedName) + } + toast?.cancel() + toast = Toast.makeText(requireContext(), toastMessage, Toast.LENGTH_LONG).also { + it.show() } renaming = true @@ -133,8 +120,21 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment lifecycleScope.launch { ensureEuiccChannelManager() euiccChannelManagerService.waitForForegroundTask() - euiccChannelManagerService.launchProfileRenameTask(slotId, portId, iccid, editedName) - .waitDone() + try { + euiccChannelManagerService + .launchProfileRenameTask(slotId, portId, iccid, editedName) + .waitDone() + } catch (e: NicknameException) { + val resId = when (e.kind) { + SetFailedKind.NicknameTooLong -> R.string.toast_profile_name_too_long + SetFailedKind.InvalidUTF8Sequence -> R.string.toast_profile_name_encode_failed + } + toast?.cancel() + toast = Toast.makeText(requireContext(), resId, Toast.LENGTH_LONG).also { + it.show() + } + return@launch + } if (parentFragment is EuiccProfilesChangedListener) { (parentFragment as EuiccProfilesChangedListener).onEuiccProfilesChanged() diff --git a/app-common/src/main/res/values/strings.xml b/app-common/src/main/res/values/strings.xml index d5f7a5c..a4e35fb 100644 --- a/app-common/src/main/res/values/strings.xml +++ b/app-common/src/main/res/values/strings.xml @@ -29,10 +29,10 @@ Cannot switch to new eSIM profile. The profile name will be updated from \'%s\' to \'%s\' - Nickname has not changed + Nickname has is unchanged Nickname cannot be longer than 64 characters - The input string could not be encoded - This profile name will be restored to the default + could not be encoded as UTF-8 + This profile name will be restored to default ICCID copied to clipboard Select Slot diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LocalProfileAssistant.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LocalProfileAssistant.kt index 4ff65fa..42eca18 100644 --- a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LocalProfileAssistant.kt +++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LocalProfileAssistant.kt @@ -12,6 +12,14 @@ interface LocalProfileAssistant { val lastApduException: Exception?, ) : Exception("Failed to download profile") + data class ProfileNicknameException(val kind: Kind) : + Exception("Failed to set nickname profile") { + enum class Kind { + NicknameTooLong, + InvalidUTF8Sequence + } + } + val valid: Boolean val profiles: List val notifications: List @@ -40,9 +48,7 @@ interface LocalProfileAssistant { fun euiccMemoryReset() - fun setNickname( - iccid: String, nickname: String - ): Boolean + fun setNickname(iccid: String, nickname: String): Boolean fun close() } \ No newline at end of file diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt index b617f2b..419d0e1 100644 --- a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt +++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt @@ -239,8 +239,24 @@ class LocalProfileAssistantImpl( } == 0 @Synchronized - override fun setNickname(iccid: String, nickname: String): Boolean = - LpacJni.es10cSetNickname(contextHandle, iccid, nickname) == 0 + override fun setNickname(iccid: String, nickname: String): Boolean { + try { + // SGP.22 v2.2.2 (Page 205 of 268) + // https://www.gsma.com/solutions-and-impact/technologies/esim/wp-content/uploads/2020/06/SGP.22-v2.2.2.pdf + // ASN.1 definition is `profileNickname [16] UTF8String (SIZE(0..64))` + val length = nickname.toByteArray(Charsets.UTF_8).size + if (length > 64) { + throw LocalProfileAssistant.ProfileNicknameException( + kind = LocalProfileAssistant.ProfileNicknameException.Kind.NicknameTooLong + ) + } + } catch (e: CharacterCodingException) { + throw LocalProfileAssistant.ProfileNicknameException( + kind = LocalProfileAssistant.ProfileNicknameException.Kind.InvalidUTF8Sequence + ) + } + return LpacJni.es10cSetNickname(contextHandle, iccid, nickname) == 0 + } override fun euiccMemoryReset() { LpacJni.es10cEuiccMemoryReset(contextHandle) -- 2.45.3 From a8d336b15f2423471eb870b96d755a4eb60be5d8 Mon Sep 17 00:00:00 2001 From: septs Date: Fri, 13 Dec 2024 13:16:36 +0800 Subject: [PATCH 11/14] chore: accept reviews --- .../im/angry/openeuicc/ui/ProfileRenameFragment.kt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt index d96bf0f..f314321 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt @@ -43,11 +43,13 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment private var toast: Toast? = null - private val iccid: String - get() = requireArguments().getString(FIELD_ICCID)!! + private val iccid by lazy { + requireArguments().getString(FIELD_ICCID)!! + } - private val currentName: String - get() = requireArguments().getString(FIELD_CURRENT_NAME)!! + private val currentName by lazy { + requireArguments().getString(FIELD_CURRENT_NAME)!! + } private var renaming = false @@ -100,6 +102,7 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment } private fun rename() { + toast?.cancel() val editedName = editText.text.toString().trim() // replace \s as space (inc. new line and spaces) .replace(SPACE_PATTERN, "\u0020") @@ -108,7 +111,6 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment editedName == currentName -> getString(R.string.toast_profile_name_is_unchanged) else -> getString(R.string.toast_profile_name_changed, currentName, editedName) } - toast?.cancel() toast = Toast.makeText(requireContext(), toastMessage, Toast.LENGTH_LONG).also { it.show() } -- 2.45.3 From 60fe614a33aec2ec1b761461c578324396131bd1 Mon Sep 17 00:00:00 2001 From: septs Date: Fri, 13 Dec 2024 13:37:52 +0800 Subject: [PATCH 12/14] feat: ignore unchanged --- .../main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt index f314321..583f224 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt @@ -123,7 +123,7 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment ensureEuiccChannelManager() euiccChannelManagerService.waitForForegroundTask() try { - euiccChannelManagerService + if (editedName != currentName) euiccChannelManagerService .launchProfileRenameTask(slotId, portId, iccid, editedName) .waitDone() } catch (e: NicknameException) { -- 2.45.3 From 2da70ed42134d552db96c5849367db59e390f718 Mon Sep 17 00:00:00 2001 From: septs Date: Fri, 13 Dec 2024 14:15:30 +0800 Subject: [PATCH 13/14] chore: accept reviews --- .../openeuicc/ui/ProfileRenameFragment.kt | 86 +++++++++++++------ .../res/layout/fragment_profile_rename.xml | 3 +- 2 files changed, 59 insertions(+), 30 deletions(-) diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt index 583f224..1926790 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt @@ -1,6 +1,7 @@ package im.angry.openeuicc.ui import android.app.Dialog +import android.content.DialogInterface import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -10,6 +11,7 @@ import android.widget.ProgressBar import android.widget.Toast import androidx.appcompat.widget.Toolbar import androidx.core.view.isVisible +import androidx.core.widget.addTextChangedListener import androidx.lifecycle.lifecycleScope import com.google.android.material.textfield.TextInputLayout import im.angry.openeuicc.common.R @@ -21,8 +23,6 @@ import net.typeblog.lpac_jni.LocalProfileAssistant.ProfileNicknameException.Kind class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragmentMarker { companion object { - val SPACE_PATTERN = Regex("\\s", RegexOption.MULTILINE) - const val TAG = "ProfileRenameFragment" const val FIELD_ICCID = "iccid" const val FIELD_CURRENT_NAME = "currentName" @@ -42,6 +42,14 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment private lateinit var progress: ProgressBar private var toast: Toast? = null + set(toast) { + field?.cancel() + field = toast + field?.show() + } + + private val editedName: String + get() = editText.text.toString().trim() private val iccid by lazy { requireArguments().getString(FIELD_ICCID)!! @@ -58,16 +66,12 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment container: ViewGroup?, savedInstanceState: Bundle? ): View { - val view = inflater.inflate(R.layout.fragment_profile_rename, container, false) - - toolbar = view.requireViewById(R.id.toolbar) - editText = view.requireViewById(R.id.profile_rename_new_name).let { - it.editText!! + val view = inflater.inflate(R.layout.fragment_profile_rename, container, false).apply { + toolbar = requireViewById(R.id.toolbar) + editText = requireViewById(R.id.profile_rename_new_name).editText!! + progress = requireViewById(R.id.progress) } - progress = view.requireViewById(R.id.progress) - toolbar.inflateMenu(R.menu.fragment_profile_rename) - return view } @@ -83,6 +87,11 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment true } } + editText.addTextChangedListener { + val isUnchanged = it.toString().trim() == currentName + dialog!!.setCancelable(isUnchanged) + dialog!!.setCanceledOnTouchOutside(isUnchanged) + } } override fun onStart() { @@ -95,6 +104,15 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment setWidthPercent(95) } + override fun onCancel(dialog: DialogInterface) { + super.onCancel(dialog) + toast = Toast.makeText( + requireContext(), + R.string.toast_profile_name_is_unchanged, + Toast.LENGTH_LONG + ) + } + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { return super.onCreateDialog(savedInstanceState).also { it.setCanceledOnTouchOutside(false) @@ -102,17 +120,24 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment } private fun rename() { - toast?.cancel() - val editedName = editText.text.toString().trim() - // replace \s as space (inc. new line and spaces) - .replace(SPACE_PATTERN, "\u0020") - val toastMessage = when { - editedName.isEmpty() -> getString(R.string.toast_profile_name_restore_defaults) - editedName == currentName -> getString(R.string.toast_profile_name_is_unchanged) - else -> getString(R.string.toast_profile_name_changed, currentName, editedName) - } - toast = Toast.makeText(requireContext(), toastMessage, Toast.LENGTH_LONG).also { - it.show() + toast = when { + editedName.isEmpty() -> Toast.makeText( + requireContext(), + R.string.toast_profile_name_restore_defaults, + Toast.LENGTH_LONG + ) + + editedName == currentName -> Toast.makeText( + requireContext(), + R.string.toast_profile_name_is_unchanged, + Toast.LENGTH_LONG + ) + + else -> Toast.makeText( + requireContext(), + getString(R.string.toast_profile_name_changed, currentName, editedName), + Toast.LENGTH_LONG + ) } renaming = true @@ -127,13 +152,18 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment .launchProfileRenameTask(slotId, portId, iccid, editedName) .waitDone() } catch (e: NicknameException) { - val resId = when (e.kind) { - SetFailedKind.NicknameTooLong -> R.string.toast_profile_name_too_long - SetFailedKind.InvalidUTF8Sequence -> R.string.toast_profile_name_encode_failed - } - toast?.cancel() - toast = Toast.makeText(requireContext(), resId, Toast.LENGTH_LONG).also { - it.show() + toast = when (e.kind) { + SetFailedKind.NicknameTooLong -> Toast.makeText( + requireContext(), + R.string.toast_profile_name_too_long, + Toast.LENGTH_LONG + ) + + SetFailedKind.InvalidUTF8Sequence -> Toast.makeText( + requireContext(), + R.string.toast_profile_name_encode_failed, + Toast.LENGTH_LONG + ) } return@launch } diff --git a/app-common/src/main/res/layout/fragment_profile_rename.xml b/app-common/src/main/res/layout/fragment_profile_rename.xml index 32c0b78..f0da20d 100644 --- a/app-common/src/main/res/layout/fragment_profile_rename.xml +++ b/app-common/src/main/res/layout/fragment_profile_rename.xml @@ -48,8 +48,7 @@ + android:layout_height="match_parent" /> -- 2.45.3 From 203963e409a9b05d6d799edc9682c51b87907d62 Mon Sep 17 00:00:00 2001 From: septs Date: Fri, 13 Dec 2024 14:19:19 +0800 Subject: [PATCH 14/14] revert: EuiccChannelManagerService --- .../im/angry/openeuicc/service/EuiccChannelManagerService.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 72ad6dc..c4d16df 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 @@ -415,7 +415,10 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker { R.drawable.ic_task_rename ) { val res = euiccChannelManager.withEuiccChannel(slotId, portId) { channel -> - channel.lpa.setNickname(iccid, name) + channel.lpa.setNickname( + iccid, + name + ) } if (!res) { -- 2.45.3