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..9e900b3 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_task_delete + ) { + 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 e02b7be..a23c166 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 @@ -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 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..418e156 --- /dev/null +++ b/app-common/src/main/java/im/angry/openeuicc/ui/EuiccMemoryResetFragment.kt @@ -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() + } +} \ 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 5674c0d..d5e7c8b 100644 --- a/app-common/src/main/res/values/strings.xml +++ b/app-common/src/main/res/values/strings.xml @@ -30,6 +30,7 @@ Cannot switch to new eSIM profile. Confirmation string mismatch + Confirmation string mismatch ICCID copied to clipboard Serial Number copied to clipboard EID copied to clipboard @@ -48,6 +49,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+) @@ -145,7 +148,10 @@ Erase the Chip Erase the Chip - I confirm to clear all profiles on this chip and understand that this operation is irreversible. + I 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 erase the chip + I CONFIRM TO ERASE ALL PROFILES WITH EID ENDING WITH %s AND UNDERSTAND THIS IRREVERSIBLE + Erase Yes No