Compare commits
1 commit
14c09d9195
...
07f57d5c7f
Author | SHA1 | Date | |
---|---|---|---|
07f57d5c7f |
8 changed files with 218 additions and 21 deletions
|
@ -495,4 +495,19 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker {
|
||||||
preferenceRepository.notificationSwitchFlow.first()
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -31,6 +31,7 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
import net.typeblog.lpac_jni.LocalProfileInfo
|
import net.typeblog.lpac_jni.LocalProfileInfo
|
||||||
import im.angry.openeuicc.common.R
|
import im.angry.openeuicc.common.R
|
||||||
|
import im.angry.openeuicc.core.EuiccChannelManager
|
||||||
import im.angry.openeuicc.service.EuiccChannelManagerService
|
import im.angry.openeuicc.service.EuiccChannelManagerService
|
||||||
import im.angry.openeuicc.service.EuiccChannelManagerService.Companion.waitDone
|
import im.angry.openeuicc.service.EuiccChannelManagerService.Companion.waitDone
|
||||||
import im.angry.openeuicc.ui.wizard.DownloadWizardActivity
|
import im.angry.openeuicc.ui.wizard.DownloadWizardActivity
|
||||||
|
@ -55,6 +56,7 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
|
||||||
private lateinit var fab: FloatingActionButton
|
private lateinit var fab: FloatingActionButton
|
||||||
private lateinit var profileList: RecyclerView
|
private lateinit var profileList: RecyclerView
|
||||||
private var logicalSlotId: Int = -1
|
private var logicalSlotId: Int = -1
|
||||||
|
private var eid: String? = null
|
||||||
|
|
||||||
private val adapter = EuiccProfileAdapter()
|
private val adapter = EuiccProfileAdapter()
|
||||||
|
|
||||||
|
@ -131,25 +133,36 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
|
||||||
inflater.inflate(R.menu.fragment_euicc, menu)
|
inflater.inflate(R.menu.fragment_euicc, menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean =
|
override fun onPrepareOptionsMenu(menu: Menu) {
|
||||||
when (item.itemId) {
|
super.onPrepareOptionsMenu(menu)
|
||||||
|
menu.findItem(R.id.show_notifications).isVisible =
|
||||||
|
logicalSlotId != -1
|
||||||
|
menu.findItem(R.id.euicc_info).isVisible =
|
||||||
|
logicalSlotId != -1
|
||||||
|
menu.findItem(R.id.euicc_memory_reset).isVisible =
|
||||||
|
logicalSlotId == EuiccChannelManager.USB_CHANNEL_ID
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||||
R.id.show_notifications -> {
|
R.id.show_notifications -> {
|
||||||
if (logicalSlotId != -1) {
|
|
||||||
Intent(requireContext(), NotificationsActivity::class.java).apply {
|
Intent(requireContext(), NotificationsActivity::class.java).apply {
|
||||||
putExtra("logicalSlotId", logicalSlotId)
|
putExtra("logicalSlotId", logicalSlotId)
|
||||||
startActivity(this)
|
startActivity(this)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.euicc_info -> {
|
R.id.euicc_info -> {
|
||||||
if (logicalSlotId != -1) {
|
|
||||||
Intent(requireContext(), EuiccInfoActivity::class.java).apply {
|
Intent(requireContext(), EuiccInfoActivity::class.java).apply {
|
||||||
putExtra("logicalSlotId", logicalSlotId)
|
putExtra("logicalSlotId", logicalSlotId)
|
||||||
startActivity(this)
|
startActivity(this)
|
||||||
}
|
}
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
R.id.euicc_memory_reset -> {
|
||||||
|
EuiccMemoryResetFragment.newInstance(slotId, portId, eid!!)
|
||||||
|
.show(childFragmentManager, EuiccMemoryResetFragment.TAG)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,6 +205,7 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
|
||||||
|
|
||||||
val profiles = withEuiccChannel { channel ->
|
val profiles = withEuiccChannel { channel ->
|
||||||
logicalSlotId = channel.logicalSlotId
|
logicalSlotId = channel.logicalSlotId
|
||||||
|
eid = channel.lpa.eID
|
||||||
euiccChannelManager.notifyEuiccProfilesChanged(channel.logicalSlotId)
|
euiccChannelManager.notifyEuiccProfilesChanged(channel.logicalSlotId)
|
||||||
if (unfilteredProfileListFlow.value)
|
if (unfilteredProfileListFlow.value)
|
||||||
channel.lpa.profiles
|
channel.lpa.profiles
|
||||||
|
|
|
@ -0,0 +1,136 @@
|
||||||
|
package im.angry.openeuicc.ui
|
||||||
|
|
||||||
|
import android.graphics.Typeface
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.text.Editable
|
||||||
|
import android.text.InputType
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
|
import android.view.inputmethod.EditorInfo
|
||||||
|
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 {
|
||||||
|
EditText(requireContext()).apply {
|
||||||
|
isLongClickable = false
|
||||||
|
typeface = Typeface.MONOSPACE
|
||||||
|
hint = Editable.Factory.getInstance()
|
||||||
|
.newEditable(getString(R.string.euicc_memory_reset_hint_text, confirmText))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
notifyChanged(parentFragment)
|
||||||
|
|
||||||
|
val resId = R.string.toast_euicc_memory_reset_finitshed
|
||||||
|
toast = Toast.makeText(requireContext(), resId, Toast.LENGTH_LONG)
|
||||||
|
|
||||||
|
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 notifyChanged(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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package im.angry.openeuicc.ui
|
package im.angry.openeuicc.ui
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
@ -77,6 +78,11 @@ open class SettingsFragment: PreferenceFragmentCompat() {
|
||||||
|
|
||||||
requirePreference<CheckBoxPreference>("pref_developer_ignore_tls_certificate")
|
requirePreference<CheckBoxPreference>("pref_developer_ignore_tls_certificate")
|
||||||
.bindBooleanFlow(preferenceRepository.ignoreTLSCertificateFlow)
|
.bindBooleanFlow(preferenceRepository.ignoreTLSCertificateFlow)
|
||||||
|
|
||||||
|
requirePreference<CheckBoxPreference>("pref_developer_euicc_memory_reset").apply {
|
||||||
|
isVisible = context.packageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)
|
||||||
|
bindBooleanFlow(preferenceRepository.euiccMemoryResetFlow)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun <T : Preference> requirePreference(key: CharSequence) =
|
protected fun <T : Preference> requirePreference(key: CharSequence) =
|
||||||
|
|
|
@ -33,6 +33,7 @@ internal object PreferenceKeys {
|
||||||
val DEVELOPER_OPTIONS_ENABLED = booleanPreferencesKey("developer_options_enabled")
|
val DEVELOPER_OPTIONS_ENABLED = booleanPreferencesKey("developer_options_enabled")
|
||||||
val UNFILTERED_PROFILE_LIST = booleanPreferencesKey("unfiltered_profile_list")
|
val UNFILTERED_PROFILE_LIST = booleanPreferencesKey("unfiltered_profile_list")
|
||||||
val IGNORE_TLS_CERTIFICATE = booleanPreferencesKey("ignore_tls_certificate")
|
val IGNORE_TLS_CERTIFICATE = booleanPreferencesKey("ignore_tls_certificate")
|
||||||
|
val EUICC_MEMORY_RESET = booleanPreferencesKey("euicc_memory_reset")
|
||||||
}
|
}
|
||||||
|
|
||||||
class PreferenceRepository(private val context: Context) {
|
class PreferenceRepository(private val context: Context) {
|
||||||
|
@ -50,6 +51,7 @@ class PreferenceRepository(private val context: Context) {
|
||||||
val developerOptionsEnabledFlow = bindFlow(PreferenceKeys.DEVELOPER_OPTIONS_ENABLED, false)
|
val developerOptionsEnabledFlow = bindFlow(PreferenceKeys.DEVELOPER_OPTIONS_ENABLED, false)
|
||||||
val unfilteredProfileListFlow = bindFlow(PreferenceKeys.UNFILTERED_PROFILE_LIST, false)
|
val unfilteredProfileListFlow = bindFlow(PreferenceKeys.UNFILTERED_PROFILE_LIST, false)
|
||||||
val ignoreTLSCertificateFlow = bindFlow(PreferenceKeys.IGNORE_TLS_CERTIFICATE, false)
|
val ignoreTLSCertificateFlow = bindFlow(PreferenceKeys.IGNORE_TLS_CERTIFICATE, false)
|
||||||
|
val euiccMemoryResetFlow = bindFlow(PreferenceKeys.EUICC_MEMORY_RESET, false)
|
||||||
|
|
||||||
private fun <T> bindFlow(key: Preferences.Key<T>, defaultValue: T): PreferenceFlowWrapper<T> =
|
private fun <T> bindFlow(key: Preferences.Key<T>, defaultValue: T): PreferenceFlowWrapper<T> =
|
||||||
PreferenceFlowWrapper(context, key, defaultValue)
|
PreferenceFlowWrapper(context, key, defaultValue)
|
||||||
|
|
|
@ -10,4 +10,9 @@
|
||||||
android:id="@+id/euicc_info"
|
android:id="@+id/euicc_info"
|
||||||
android:title="@string/euicc_info"
|
android:title="@string/euicc_info"
|
||||||
app:showAsAction="never" />
|
app:showAsAction="never" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/euicc_memory_reset"
|
||||||
|
android:title="@string/euicc_memory_reset"
|
||||||
|
app:showAsAction="never" />
|
||||||
</menu>
|
</menu>
|
|
@ -30,6 +30,8 @@
|
||||||
|
|
||||||
<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_delete_confirm_text_mismatched">Confirmation string mismatch</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_euicc_memory_reset_finitshed">This eSIM chip has been erased</string>
|
||||||
<string name="toast_iccid_copied">ICCID copied to clipboard</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_sn_copied">Serial Number copied to clipboard</string>
|
||||||
<string name="toast_eid_copied">EID copied to clipboard</string>
|
<string name="toast_eid_copied">EID copied to clipboard</string>
|
||||||
|
@ -48,6 +50,8 @@
|
||||||
<string name="task_profile_delete_failure">Failed to delete eSIM profile</string>
|
<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">Switching eSIM profile</string>
|
||||||
<string name="task_profile_switch_failure">Failed to switch 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">New eSIM</string>
|
||||||
<string name="profile_download_server">Server (RSP / SM-DP+)</string>
|
<string name="profile_download_server">Server (RSP / SM-DP+)</string>
|
||||||
|
@ -143,6 +147,13 @@
|
||||||
<string name="euicc_info_ci_unknown">Unknown eSIM CI</string>
|
<string name="euicc_info_ci_unknown">Unknown eSIM CI</string>
|
||||||
<string name="euicc_info_atr" translatable="false">Answer To Reset (ATR)</string>
|
<string name="euicc_info_atr" translatable="false">Answer To Reset (ATR)</string>
|
||||||
|
|
||||||
|
<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 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="yes">Yes</string>
|
||||||
<string name="no">No</string>
|
<string name="no">No</string>
|
||||||
|
|
||||||
|
@ -175,6 +186,8 @@
|
||||||
<string name="pref_developer_unfiltered_profile_list_desc">Include non-production profiles in the list</string>
|
<string name="pref_developer_unfiltered_profile_list_desc">Include non-production profiles in the list</string>
|
||||||
<string name="pref_developer_ignore_tls_certificate">Ignore SM-DP+ TLS certificate</string>
|
<string name="pref_developer_ignore_tls_certificate">Ignore SM-DP+ TLS certificate</string>
|
||||||
<string name="pref_developer_ignore_tls_certificate_desc">Accept any TLS certificate used by the RSP server</string>
|
<string name="pref_developer_ignore_tls_certificate_desc">Accept any TLS certificate used by the RSP server</string>
|
||||||
|
<string name="pref_developer_euicc_memory_reset">Allow Erase Chip (USB only)</string>s
|
||||||
|
<string name="pref_developer_euicc_memory_reset_desc">Don\'t erase your eSIM as a troubleshooting step unless directed to by your carrier.</string>
|
||||||
<string name="pref_info">Info</string>
|
<string name="pref_info">Info</string>
|
||||||
<string name="pref_info_app_version">App Version</string>
|
<string name="pref_info_app_version">App Version</string>
|
||||||
<string name="pref_info_source_code">Source Code</string>
|
<string name="pref_info_source_code">Source Code</string>
|
||||||
|
|
|
@ -69,6 +69,12 @@
|
||||||
app:summary="@string/pref_developer_ignore_tls_certificate_desc"
|
app:summary="@string/pref_developer_ignore_tls_certificate_desc"
|
||||||
app:title="@string/pref_developer_ignore_tls_certificate" />
|
app:title="@string/pref_developer_ignore_tls_certificate" />
|
||||||
|
|
||||||
|
<CheckBoxPreference
|
||||||
|
app:iconSpaceReserved="false"
|
||||||
|
app:isPreferenceVisible="false"
|
||||||
|
app:key="pref_developer_euicc_memory_reset"
|
||||||
|
app:summary="@string/pref_developer_euicc_memory_reset_desc"
|
||||||
|
app:title="@string/pref_developer_euicc_memory_reset" />
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
|
|
Loading…
Add table
Reference in a new issue