feat: profile rename more toast hints #111

Closed
septs wants to merge 15 commits from septs:profile-rename-strong-checking into master
4 changed files with 119 additions and 27 deletions

View file

@ -1,38 +1,64 @@
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
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.core.widget.addTextChangedListener
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 net.typeblog.lpac_jni.LocalProfileAssistant.ProfileNicknameException as NicknameException
import net.typeblog.lpac_jni.LocalProfileAssistant.ProfileNicknameException.Kind as SetFailedKind
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 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)!!
}
private val currentName by lazy {
requireArguments().getString(FIELD_CURRENT_NAME)!!
}
private var renaming = false
override fun onCreateView(
@ -40,14 +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)
profileRenameNewName = view.requireViewById(R.id.profile_rename_new_name)
progress = view.requireViewById(R.id.progress)
val view = inflater.inflate(R.layout.fragment_profile_rename, container, false).apply {
toolbar = requireViewById(R.id.toolbar)
editText = requireViewById<TextInputLayout>(R.id.profile_rename_new_name).editText!!
progress = requireViewById(R.id.progress)
}
toolbar.inflateMenu(R.menu.fragment_profile_rename)
return view
}
@ -63,11 +87,16 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment
true
}
}
editText.addTextChangedListener {
val isUnchanged = it.toString().trim() == currentName
dialog!!.setCancelable(isUnchanged)
dialog!!.setCanceledOnTouchOutside(isUnchanged)
}
}
override fun onStart() {
super.onStart()
profileRenameNewName.editText!!.setText(requireArguments().getString("currentName"))
editText.setText(currentName)
}
override fun onResume() {
@ -75,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)
@ -82,25 +120,53 @@ 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()
return
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
progress.isIndeterminate = true
progress.visibility = View.VISIBLE
progress.isVisible = true
lifecycleScope.launch {
ensureEuiccChannelManager()
euiccChannelManagerService.waitForForegroundTask()
euiccChannelManagerService.launchProfileRenameTask(
slotId,
portId,
requireArguments().getString("iccid")!!,
name
).waitDone()
try {
if (editedName != currentName) euiccChannelManagerService
.launchProfileRenameTask(slotId, portId, iccid, editedName)
.waitDone()
} catch (e: NicknameException) {
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
}
if (parentFragment is EuiccProfilesChangedListener) {
(parentFragment as EuiccProfilesChangedListener).onEuiccProfilesChanged()

View file

@ -28,7 +28,11 @@
<string name="switch_did_not_refresh">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.</string>
<string name="toast_profile_enable_failed">Cannot switch to new eSIM profile.</string>
<string name="toast_profile_name_changed">The profile name will be updated from \'%s\' to \'%s\'</string>
<string name="toast_profile_name_is_unchanged">Nickname has is unchanged</string>

is unchanged

is unchanged
<string name="toast_profile_name_too_long">Nickname cannot be longer than 64 characters</string>
<string name="toast_profile_name_encode_failed">could not be encoded as UTF-8</string>

could not be encoded as UTF-8

could not be encoded as UTF-8
<string name="toast_profile_name_restore_defaults">This profile name will be restored to default</string>

delete "the"

delete "the"
<string name="toast_profile_delete_confirm_text_mismatched">Confirmation string mismatch</string>
<string name="toast_iccid_copied">ICCID copied to clipboard</string>

View file

@ -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<LocalProfileInfo>
val notifications: List<LocalProfileNotification>
@ -40,9 +48,7 @@ interface LocalProfileAssistant {
fun euiccMemoryReset()
fun setNickname(
iccid: String, nickname: String
): Boolean
fun setNickname(iccid: String, nickname: String): Boolean
fun close()
}

View file

@ -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)