Compare commits

..

1 commit

Author SHA1 Message Date
89b5e33cd2
feat: euicc memory reset 2025-03-06 20:17:19 +08:00
4 changed files with 155 additions and 33 deletions

View file

@ -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_task_delete
) {
euiccChannelManager.beginTrackedOperation(slotId, portId) {
euiccChannelManager.withEuiccChannel(slotId, portId) { channel ->
channel.lpa.euiccMemoryReset()
}
preferenceRepository.euiccMemoryResetFlow.first()
}
}
}

View file

@ -56,6 +56,7 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
private lateinit var fab: FloatingActionButton
private lateinit var profileList: RecyclerView
private var logicalSlotId: Int = -1
private var eid: String? = null
private val adapter = EuiccProfileAdapter()
@ -160,38 +161,8 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
}
R.id.euicc_memory_reset -> {
AlertDialog.Builder(requireContext()).apply {
setTitle(R.string.euicc_memory_reset_title)
setMessage(R.string.euicc_memory_reset_message)
setPositiveButton(android.R.string.ok) { dialog, _ ->
dialog.dismiss()
lifecycleScope.launch {
ensureEuiccChannelManager()
euiccChannelManagerService.waitForForegroundTask()
// val err = euiccChannelManagerService.launchMemoryResetTask(
// slotId,
// portId
// ).waitDone()
//
// if (err != null) {
// withContext(Dispatchers.Main) {
// AlertDialog.Builder(requireContext()).apply {
// setMessage(R.string.euicc_memory_reset_failed)
// setPositiveButton(android.R.string.ok) { dialog, _ ->
// dialog.dismiss()
// }
// show()
// }
// }
// }
}
}
setNegativeButton(android.R.string.cancel) { dialog, _ ->
dialog.dismiss()
}
show()
}
EuiccMemoryResetFragment.newInstance(slotId, portId, eid!!)
.show(childFragmentManager, EuiccMemoryResetFragment.TAG)
true
}
@ -234,6 +205,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

View file

@ -0,0 +1,129 @@
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.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).apply {
requireArguments().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 {
val edit = EditText(requireContext())
edit.isLongClickable = false
edit.typeface = Typeface.MONOSPACE
edit.hint = Editable.Factory.getInstance()
.newEditable(getString(R.string.euicc_memory_reset_hint_text, confirmText))
edit
}
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 {
emitEuiccProfilesChanged(parentFragment)
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
}
}
private fun emitEuiccProfilesChanged(fragment: Fragment?) {
if (fragment is EuiccProfilesChangedListener) {
// Trigger a refresh in the parent fragment -- it should wait until
// any foreground task is completed before actually doing a refresh
fragment.onEuiccProfilesChanged()
}
}

View file

@ -30,6 +30,7 @@
<string name="toast_profile_enable_failed">Cannot switch to new eSIM profile.</string>
<string name="toast_profile_delete_confirm_text_mismatched">Confirmation string mismatch</string>
<string name="toast_euicc_memory_reset_confirm_text_mismatched">Confirmation string mismatch</string>
<string name="toast_iccid_copied">ICCID copied to clipboard</string>
<string name="toast_sn_copied">Serial Number copied to clipboard</string>
<string name="toast_eid_copied">EID copied to clipboard</string>
@ -48,6 +49,8 @@
<string name="task_profile_delete_failure">Failed to delete eSIM profile</string>
<string name="task_profile_switch">Switching eSIM profile</string>
<string name="task_profile_switch_failure">Failed to switch eSIM profile</string>
<string name="task_euicc_memory_reset">Erasing eSIM chip</string>
<string name="task_euicc_memory_reset_failure">Failed to erase eSIM chip</string>
<string name="profile_download">New eSIM</string>
<string name="profile_download_server">Server (RSP / SM-DP+)</string>
@ -145,7 +148,10 @@
<string name="euicc_memory_reset">Erase the Chip</string>
<string name="euicc_memory_reset_title">Erase the Chip</string>
<string name="euicc_memory_reset_message">I confirm to clear all profiles on this chip and understand that this operation is irreversible.</string>
<string name="euicc_memory_reset_message">I confirm to delete all profiles on this chip and understand that this operation is irreversible.\n\nEID: %s\n\n%s</string>
<string name="euicc_memory_reset_hint_text">Type \'%s\' here to confirm erase the chip</string>
<string name="euicc_memory_reset_confirm_text">I CONFIRM TO ERASE ALL PROFILES WITH EID ENDING WITH %s AND UNDERSTAND THIS IRREVERSIBLE</string>
<string name="euicc_memory_reset_invoke_button">Erase</string>
<string name="yes">Yes</string>
<string name="no">No</string>