feat: profile rename more toast hints #111
4 changed files with 119 additions and 27 deletions
|
@ -1,38 +1,64 @@
|
||||||
package im.angry.openeuicc.ui
|
package im.angry.openeuicc.ui
|
||||||
|
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
|
import android.content.DialogInterface
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.widget.EditText
|
||||||
import android.widget.ProgressBar
|
import android.widget.ProgressBar
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.core.widget.addTextChangedListener
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import im.angry.openeuicc.common.R
|
import im.angry.openeuicc.common.R
|
||||||
import im.angry.openeuicc.service.EuiccChannelManagerService.Companion.waitDone
|
import im.angry.openeuicc.service.EuiccChannelManagerService.Companion.waitDone
|
||||||
import im.angry.openeuicc.util.*
|
import im.angry.openeuicc.util.*
|
||||||
import kotlinx.coroutines.launch
|
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 {
|
class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragmentMarker {
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "ProfileRenameFragment"
|
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 {
|
fun newInstance(slotId: Int, portId: Int, iccid: String, currentName: String): ProfileRenameFragment {
|
||||||
val instance = newInstanceEuicc(ProfileRenameFragment::class.java, slotId, portId)
|
val instance = newInstanceEuicc(ProfileRenameFragment::class.java, slotId, portId)
|
||||||
instance.requireArguments().apply {
|
instance.requireArguments().apply {
|
||||||
putString("iccid", iccid)
|
putString(FIELD_ICCID, iccid)
|
||||||
putString("currentName", currentName)
|
putString(FIELD_CURRENT_NAME, currentName)
|
||||||
}
|
}
|
||||||
return instance
|
return instance
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var toolbar: Toolbar
|
private lateinit var toolbar: Toolbar
|
||||||
private lateinit var profileRenameNewName: TextInputLayout
|
private lateinit var editText: EditText
|
||||||
private lateinit var progress: ProgressBar
|
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
|
private var renaming = false
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
|
@ -40,14 +66,12 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View {
|
): View {
|
||||||
val view = inflater.inflate(R.layout.fragment_profile_rename, container, false)
|
val view = inflater.inflate(R.layout.fragment_profile_rename, container, false).apply {
|
||||||
|
toolbar = requireViewById(R.id.toolbar)
|
||||||
toolbar = view.requireViewById(R.id.toolbar)
|
editText = requireViewById<TextInputLayout>(R.id.profile_rename_new_name).editText!!
|
||||||
profileRenameNewName = view.requireViewById(R.id.profile_rename_new_name)
|
progress = requireViewById(R.id.progress)
|
||||||
progress = view.requireViewById(R.id.progress)
|
}
|
||||||
|
|
||||||
toolbar.inflateMenu(R.menu.fragment_profile_rename)
|
toolbar.inflateMenu(R.menu.fragment_profile_rename)
|
||||||
|
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,11 +87,16 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
editText.addTextChangedListener {
|
||||||
|
val isUnchanged = it.toString().trim() == currentName
|
||||||
|
dialog!!.setCancelable(isUnchanged)
|
||||||
|
dialog!!.setCanceledOnTouchOutside(isUnchanged)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
profileRenameNewName.editText!!.setText(requireArguments().getString("currentName"))
|
editText.setText(currentName)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
|
@ -75,6 +104,15 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment
|
||||||
setWidthPercent(95)
|
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 {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
return super.onCreateDialog(savedInstanceState).also {
|
return super.onCreateDialog(savedInstanceState).also {
|
||||||
it.setCanceledOnTouchOutside(false)
|
it.setCanceledOnTouchOutside(false)
|
||||||
|
@ -82,25 +120,53 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun rename() {
|
private fun rename() {
|
||||||
val name = profileRenameNewName.editText!!.text.toString().trim()
|
toast = when {
|
||||||
if (name.length >= 64) {
|
editedName.isEmpty() -> Toast.makeText(
|
||||||
Toast.makeText(context, R.string.toast_profile_name_too_long, Toast.LENGTH_LONG).show()
|
requireContext(),
|
||||||
return
|
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
|
renaming = true
|
||||||
progress.isIndeterminate = true
|
progress.isIndeterminate = true
|
||||||
progress.visibility = View.VISIBLE
|
progress.isVisible = true
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
ensureEuiccChannelManager()
|
ensureEuiccChannelManager()
|
||||||
euiccChannelManagerService.waitForForegroundTask()
|
euiccChannelManagerService.waitForForegroundTask()
|
||||||
euiccChannelManagerService.launchProfileRenameTask(
|
try {
|
||||||
slotId,
|
if (editedName != currentName) euiccChannelManagerService
|
||||||
portId,
|
.launchProfileRenameTask(slotId, portId, iccid, editedName)
|
||||||
requireArguments().getString("iccid")!!,
|
.waitDone()
|
||||||
name
|
} catch (e: NicknameException) {
|
||||||
).waitDone()
|
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) {
|
if (parentFragment is EuiccProfilesChangedListener) {
|
||||||
(parentFragment as EuiccProfilesChangedListener).onEuiccProfilesChanged()
|
(parentFragment as EuiccProfilesChangedListener).onEuiccProfilesChanged()
|
||||||
|
|
|
@ -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="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_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>
|
||||||
|
|||||||
<string name="toast_profile_name_too_long">Nickname cannot be longer than 64 characters</string>
|
<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>
|
||||||
PeterCxy
commented
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>
|
||||||
PeterCxy
commented
delete "the" delete "the"
|
|||||||
<string name="toast_profile_delete_confirm_text_mismatched">Confirmation string mismatch</string>
|
<string name="toast_profile_delete_confirm_text_mismatched">Confirmation string mismatch</string>
|
||||||
<string name="toast_iccid_copied">ICCID copied to clipboard</string>
|
<string name="toast_iccid_copied">ICCID copied to clipboard</string>
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,14 @@ interface LocalProfileAssistant {
|
||||||
val lastApduException: Exception?,
|
val lastApduException: Exception?,
|
||||||
) : Exception("Failed to download profile")
|
) : 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 valid: Boolean
|
||||||
val profiles: List<LocalProfileInfo>
|
val profiles: List<LocalProfileInfo>
|
||||||
val notifications: List<LocalProfileNotification>
|
val notifications: List<LocalProfileNotification>
|
||||||
|
@ -40,9 +48,7 @@ interface LocalProfileAssistant {
|
||||||
|
|
||||||
fun euiccMemoryReset()
|
fun euiccMemoryReset()
|
||||||
|
|
||||||
fun setNickname(
|
fun setNickname(iccid: String, nickname: String): Boolean
|
||||||
iccid: String, nickname: String
|
|
||||||
): Boolean
|
|
||||||
|
|
||||||
fun close()
|
fun close()
|
||||||
}
|
}
|
|
@ -239,8 +239,24 @@ class LocalProfileAssistantImpl(
|
||||||
} == 0
|
} == 0
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
override fun setNickname(iccid: String, nickname: String): Boolean =
|
override fun setNickname(iccid: String, nickname: String): Boolean {
|
||||||
LpacJni.es10cSetNickname(contextHandle, iccid, nickname) == 0
|
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() {
|
override fun euiccMemoryReset() {
|
||||||
LpacJni.es10cEuiccMemoryReset(contextHandle)
|
LpacJni.es10cEuiccMemoryReset(contextHandle)
|
||||||
|
|
Loading…
Add table
Reference in a new issue
is unchanged