forked from PeterCxy/OpenEUICC
Compare commits
No commits in common. "c4b513fc0affa3ef0a3a1d3ad6adafa009590608" and "e9f4d3d1f9d4557628bd9946ffb7f72101dca6bd" have entirely different histories.
c4b513fc0a
...
e9f4d3d1f9
19 changed files with 19 additions and 698 deletions
|
@ -32,10 +32,6 @@
|
||||||
android:name="im.angry.openeuicc.ui.LogsActivity"
|
android:name="im.angry.openeuicc.ui.LogsActivity"
|
||||||
android:label="@string/pref_advanced_logs" />
|
android:label="@string/pref_advanced_logs" />
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name="im.angry.openeuicc.ui.wizard.DownloadWizardActivity"
|
|
||||||
android:label="@string/download_wizard" />
|
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="com.journeyapps.barcodescanner.CaptureActivity"
|
android:name="com.journeyapps.barcodescanner.CaptureActivity"
|
||||||
android:screenOrientation="fullSensor"
|
android:screenOrientation="fullSensor"
|
||||||
|
|
|
@ -42,8 +42,7 @@ open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccCha
|
||||||
port,
|
port,
|
||||||
context.preferenceRepository.verboseLoggingFlow
|
context.preferenceRepository.verboseLoggingFlow
|
||||||
),
|
),
|
||||||
context.preferenceRepository.verboseLoggingFlow,
|
context.preferenceRepository.verboseLoggingFlow
|
||||||
context.preferenceRepository.ignoreTLSCertificateFlow,
|
|
||||||
).also {
|
).also {
|
||||||
Log.i(DefaultEuiccChannelManager.TAG, "Is OMAPI channel, setting MSS to 60")
|
Log.i(DefaultEuiccChannelManager.TAG, "Is OMAPI channel, setting MSS to 60")
|
||||||
it.lpa.setEs10xMss(60)
|
it.lpa.setEs10xMss(60)
|
||||||
|
@ -73,8 +72,7 @@ open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccCha
|
||||||
bulkOut,
|
bulkOut,
|
||||||
context.preferenceRepository.verboseLoggingFlow
|
context.preferenceRepository.verboseLoggingFlow
|
||||||
),
|
),
|
||||||
context.preferenceRepository.verboseLoggingFlow,
|
context.preferenceRepository.verboseLoggingFlow
|
||||||
context.preferenceRepository.ignoreTLSCertificateFlow,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,15 +11,14 @@ class EuiccChannelImpl(
|
||||||
override val type: String,
|
override val type: String,
|
||||||
override val port: UiccPortInfoCompat,
|
override val port: UiccPortInfoCompat,
|
||||||
apduInterface: ApduInterface,
|
apduInterface: ApduInterface,
|
||||||
verboseLoggingFlow: Flow<Boolean>,
|
verboseLoggingFlow: Flow<Boolean>
|
||||||
ignoreTLSCertificateFlow: Flow<Boolean>
|
|
||||||
) : EuiccChannel {
|
) : EuiccChannel {
|
||||||
override val slotId = port.card.physicalSlotIndex
|
override val slotId = port.card.physicalSlotIndex
|
||||||
override val logicalSlotId = port.logicalSlotIndex
|
override val logicalSlotId = port.logicalSlotIndex
|
||||||
override val portId = port.portIndex
|
override val portId = port.portIndex
|
||||||
|
|
||||||
override val lpa: LocalProfileAssistant =
|
override val lpa: LocalProfileAssistant =
|
||||||
LocalProfileAssistantImpl(apduInterface, HttpInterfaceImpl(verboseLoggingFlow, ignoreTLSCertificateFlow))
|
LocalProfileAssistantImpl(apduInterface, HttpInterfaceImpl(verboseLoggingFlow))
|
||||||
|
|
||||||
override val valid: Boolean
|
override val valid: Boolean
|
||||||
get() = lpa.valid
|
get() = lpa.valid
|
||||||
|
|
|
@ -31,12 +31,10 @@ import net.typeblog.lpac_jni.LocalProfileInfo
|
||||||
import im.angry.openeuicc.common.R
|
import im.angry.openeuicc.common.R
|
||||||
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.util.*
|
import im.angry.openeuicc.util.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.TimeoutCancellationException
|
import kotlinx.coroutines.TimeoutCancellationException
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
@ -107,19 +105,10 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
|
||||||
LinearLayoutManager(view.context, LinearLayoutManager.VERTICAL, false)
|
LinearLayoutManager(view.context, LinearLayoutManager.VERTICAL, false)
|
||||||
|
|
||||||
fab.setOnClickListener {
|
fab.setOnClickListener {
|
||||||
lifecycleScope.launch {
|
|
||||||
if (preferenceRepository.experimentalDownloadWizardFlow.first()) {
|
|
||||||
Intent(requireContext(), DownloadWizardActivity::class.java).apply {
|
|
||||||
putExtra("selectedLogicalSlot", logicalSlotId)
|
|
||||||
startActivity(this)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ProfileDownloadFragment.newInstance(slotId, portId)
|
ProfileDownloadFragment.newInstance(slotId, portId)
|
||||||
.show(childFragmentManager, ProfileDownloadFragment.TAG)
|
.show(childFragmentManager, ProfileDownloadFragment.TAG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
|
|
|
@ -3,48 +3,26 @@ package im.angry.openeuicc.ui
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.widget.Toast
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import androidx.datastore.preferences.core.Preferences
|
import androidx.datastore.preferences.core.Preferences
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.preference.CheckBoxPreference
|
import androidx.preference.CheckBoxPreference
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceCategory
|
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
import im.angry.openeuicc.common.R
|
import im.angry.openeuicc.common.R
|
||||||
import im.angry.openeuicc.util.*
|
import im.angry.openeuicc.util.*
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.collect
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
class SettingsFragment: PreferenceFragmentCompat() {
|
class SettingsFragment: PreferenceFragmentCompat() {
|
||||||
private lateinit var developerPref: PreferenceCategory
|
|
||||||
|
|
||||||
// Hidden developer options switch
|
|
||||||
private var numClicks = 0
|
|
||||||
private var lastClickTimestamp = -1L
|
|
||||||
private var lastToast: Toast? = null
|
|
||||||
|
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
setPreferencesFromResource(R.xml.pref_settings, rootKey)
|
setPreferencesFromResource(R.xml.pref_settings, rootKey)
|
||||||
|
|
||||||
developerPref = findPreference("pref_developer")!!
|
|
||||||
|
|
||||||
// Show / hide developer preference based on whether it is enabled
|
|
||||||
lifecycleScope.launch {
|
|
||||||
preferenceRepository.developerOptionsEnabledFlow.onEach {
|
|
||||||
developerPref.isVisible = it
|
|
||||||
}.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
findPreference<Preference>("pref_info_app_version")
|
findPreference<Preference>("pref_info_app_version")
|
||||||
?.apply {
|
?.summary = requireContext().selfAppVersion
|
||||||
summary = requireContext().selfAppVersion
|
|
||||||
|
|
||||||
// Enable developer options when this is clicked for 7 times
|
|
||||||
setOnPreferenceClickListener(this@SettingsFragment::onAppVersionClicked)
|
|
||||||
}
|
|
||||||
|
|
||||||
findPreference<Preference>("pref_info_source_code")
|
findPreference<Preference>("pref_info_source_code")
|
||||||
?.setOnPreferenceClickListener {
|
?.setOnPreferenceClickListener {
|
||||||
|
@ -72,12 +50,6 @@ class SettingsFragment: PreferenceFragmentCompat() {
|
||||||
|
|
||||||
findPreference<CheckBoxPreference>("pref_advanced_verbose_logging")
|
findPreference<CheckBoxPreference>("pref_advanced_verbose_logging")
|
||||||
?.bindBooleanFlow(preferenceRepository.verboseLoggingFlow, PreferenceKeys.VERBOSE_LOGGING)
|
?.bindBooleanFlow(preferenceRepository.verboseLoggingFlow, PreferenceKeys.VERBOSE_LOGGING)
|
||||||
|
|
||||||
findPreference<CheckBoxPreference>("pref_developer_experimental_download_wizard")
|
|
||||||
?.bindBooleanFlow(preferenceRepository.experimentalDownloadWizardFlow, PreferenceKeys.EXPERIMENTAL_DOWNLOAD_WIZARD)
|
|
||||||
|
|
||||||
findPreference<CheckBoxPreference>("pref_ignore_tls_certificate")
|
|
||||||
?.bindBooleanFlow(preferenceRepository.ignoreTLSCertificateFlow, PreferenceKeys.IGNORE_TLS_CERTIFICATE)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
|
@ -85,44 +57,6 @@ class SettingsFragment: PreferenceFragmentCompat() {
|
||||||
setupRootViewInsets(requireView().requireViewById(androidx.preference.R.id.recycler_view))
|
setupRootViewInsets(requireView().requireViewById(androidx.preference.R.id.recycler_view))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNUSED_PARAMETER")
|
|
||||||
private fun onAppVersionClicked(pref: Preference): Boolean {
|
|
||||||
if (developerPref.isVisible) return false
|
|
||||||
val now = System.currentTimeMillis()
|
|
||||||
if (now - lastClickTimestamp >= 1000) {
|
|
||||||
numClicks = 1
|
|
||||||
} else {
|
|
||||||
numClicks++
|
|
||||||
}
|
|
||||||
lastClickTimestamp = now
|
|
||||||
|
|
||||||
if (numClicks == 7) {
|
|
||||||
lifecycleScope.launch {
|
|
||||||
preferenceRepository.updatePreference(
|
|
||||||
PreferenceKeys.DEVELOPER_OPTIONS_ENABLED,
|
|
||||||
true
|
|
||||||
)
|
|
||||||
|
|
||||||
lastToast?.cancel()
|
|
||||||
Toast.makeText(
|
|
||||||
requireContext(),
|
|
||||||
R.string.developer_options_enabled,
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
} else if (numClicks > 1) {
|
|
||||||
lastToast?.cancel()
|
|
||||||
lastToast = Toast.makeText(
|
|
||||||
requireContext(),
|
|
||||||
getString(R.string.developer_options_steps, 7 - numClicks),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
)
|
|
||||||
lastToast!!.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun CheckBoxPreference.bindBooleanFlow(flow: Flow<Boolean>, key: Preferences.Key<Boolean>) {
|
private fun CheckBoxPreference.bindBooleanFlow(flow: Flow<Boolean>, key: Preferences.Key<Boolean>) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
flow.collect { isChecked = it }
|
flow.collect { isChecked = it }
|
||||||
|
|
|
@ -1,130 +0,0 @@
|
||||||
package im.angry.openeuicc.ui.wizard
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.View
|
|
||||||
import android.widget.Button
|
|
||||||
import android.widget.ProgressBar
|
|
||||||
import androidx.activity.OnBackPressedCallback
|
|
||||||
import androidx.activity.enableEdgeToEdge
|
|
||||||
import androidx.core.view.ViewCompat
|
|
||||||
import androidx.core.view.WindowInsetsCompat
|
|
||||||
import androidx.core.view.updatePadding
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import im.angry.openeuicc.common.R
|
|
||||||
import im.angry.openeuicc.ui.BaseEuiccAccessActivity
|
|
||||||
import im.angry.openeuicc.util.*
|
|
||||||
|
|
||||||
class DownloadWizardActivity: BaseEuiccAccessActivity() {
|
|
||||||
data class DownloadWizardState(
|
|
||||||
var selectedLogicalSlot: Int
|
|
||||||
)
|
|
||||||
|
|
||||||
private lateinit var state: DownloadWizardState
|
|
||||||
|
|
||||||
private lateinit var progressBar: ProgressBar
|
|
||||||
private lateinit var nextButton: Button
|
|
||||||
private lateinit var prevButton: Button
|
|
||||||
|
|
||||||
private var currentFragment: DownloadWizardStepFragment? = null
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
enableEdgeToEdge()
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
setContentView(R.layout.activity_download_wizard)
|
|
||||||
onBackPressedDispatcher.addCallback(object : OnBackPressedCallback(true) {
|
|
||||||
override fun handleOnBackPressed() {
|
|
||||||
// TODO: Actually implement this
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
state = DownloadWizardState(
|
|
||||||
intent.getIntExtra("selectedLogicalSlot", 0)
|
|
||||||
)
|
|
||||||
|
|
||||||
progressBar = requireViewById(R.id.progress)
|
|
||||||
nextButton = requireViewById(R.id.download_wizard_next)
|
|
||||||
prevButton = requireViewById(R.id.download_wizard_back)
|
|
||||||
|
|
||||||
val navigation = requireViewById<View>(R.id.download_wizard_navigation)
|
|
||||||
val origHeight = navigation.layoutParams.height
|
|
||||||
|
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(navigation) { v, insets ->
|
|
||||||
val bars = insets.getInsets(
|
|
||||||
WindowInsetsCompat.Type.systemBars()
|
|
||||||
or WindowInsetsCompat.Type.displayCutout()
|
|
||||||
)
|
|
||||||
v.updatePadding(bars.left, 0, bars.right, bars.bottom)
|
|
||||||
val newParams = navigation.layoutParams
|
|
||||||
newParams.height = origHeight + bars.bottom
|
|
||||||
navigation.layoutParams = newParams
|
|
||||||
WindowInsetsCompat.CONSUMED
|
|
||||||
}
|
|
||||||
|
|
||||||
val fragmentRoot = requireViewById<View>(R.id.step_fragment_container)
|
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(fragmentRoot) { v, insets ->
|
|
||||||
val bars = insets.getInsets(
|
|
||||||
WindowInsetsCompat.Type.systemBars()
|
|
||||||
or WindowInsetsCompat.Type.displayCutout()
|
|
||||||
)
|
|
||||||
v.updatePadding(bars.left, bars.top, bars.right, 0)
|
|
||||||
WindowInsetsCompat.CONSUMED
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onInit() {
|
|
||||||
progressBar.visibility = View.GONE
|
|
||||||
showFragment(DownloadWizardSlotSelectFragment())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showFragment(nextFrag: DownloadWizardStepFragment) {
|
|
||||||
currentFragment = nextFrag
|
|
||||||
supportFragmentManager.beginTransaction().replace(R.id.step_fragment_container, nextFrag)
|
|
||||||
.commit()
|
|
||||||
refreshButtons()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun refreshButtons() {
|
|
||||||
currentFragment?.let {
|
|
||||||
nextButton.visibility = if (it.hasNext) {
|
|
||||||
View.VISIBLE
|
|
||||||
} else {
|
|
||||||
View.GONE
|
|
||||||
}
|
|
||||||
prevButton.visibility = if (it.hasPrev) {
|
|
||||||
View.VISIBLE
|
|
||||||
} else {
|
|
||||||
View.GONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class DownloadWizardStepFragment : Fragment(), OpenEuiccContextMarker {
|
|
||||||
protected val state: DownloadWizardState
|
|
||||||
get() = (requireActivity() as DownloadWizardActivity).state
|
|
||||||
|
|
||||||
abstract val hasNext: Boolean
|
|
||||||
abstract val hasPrev: Boolean
|
|
||||||
abstract fun createNextFragment(): DownloadWizardStepFragment?
|
|
||||||
abstract fun createPrevFragment(): DownloadWizardStepFragment?
|
|
||||||
|
|
||||||
protected fun hideProgressBar() {
|
|
||||||
(requireActivity() as DownloadWizardActivity).progressBar.visibility = View.GONE
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun showProgressBar(progressValue: Int) {
|
|
||||||
(requireActivity() as DownloadWizardActivity).progressBar.apply {
|
|
||||||
visibility = View.VISIBLE
|
|
||||||
if (progressValue >= 0) {
|
|
||||||
isIndeterminate = false
|
|
||||||
progress = progressValue
|
|
||||||
} else {
|
|
||||||
isIndeterminate = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun refreshButtons() {
|
|
||||||
(requireActivity() as DownloadWizardActivity).refreshButtons()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,165 +0,0 @@
|
||||||
package im.angry.openeuicc.ui.wizard
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.CheckBox
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.recyclerview.widget.DividerItemDecoration
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
|
||||||
import im.angry.openeuicc.common.R
|
|
||||||
import im.angry.openeuicc.util.*
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.flow.toList
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import net.typeblog.lpac_jni.LocalProfileInfo
|
|
||||||
|
|
||||||
class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardStepFragment() {
|
|
||||||
private data class SlotInfo(
|
|
||||||
val logicalSlotId: Int,
|
|
||||||
val isRemovable: Boolean,
|
|
||||||
val hasMultiplePorts: Boolean,
|
|
||||||
val portId: Int,
|
|
||||||
val eID: String,
|
|
||||||
val enabledProfileName: String?
|
|
||||||
)
|
|
||||||
|
|
||||||
private var loaded = false
|
|
||||||
|
|
||||||
private val adapter = SlotInfoAdapter()
|
|
||||||
|
|
||||||
override val hasNext: Boolean
|
|
||||||
get() = loaded && adapter.slots.isNotEmpty()
|
|
||||||
override val hasPrev: Boolean
|
|
||||||
get() = true
|
|
||||||
|
|
||||||
override fun createNextFragment(): DownloadWizardActivity.DownloadWizardStepFragment? {
|
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun createPrevFragment(): DownloadWizardActivity.DownloadWizardStepFragment? = null
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater,
|
|
||||||
container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?
|
|
||||||
): View? {
|
|
||||||
val view = inflater.inflate(R.layout.fragment_download_slot_select, container, false)
|
|
||||||
val recyclerView = view.requireViewById<RecyclerView>(R.id.download_slot_list)
|
|
||||||
recyclerView.adapter = adapter
|
|
||||||
recyclerView.layoutManager =
|
|
||||||
LinearLayoutManager(view.context, LinearLayoutManager.VERTICAL, false)
|
|
||||||
recyclerView.addItemDecoration(DividerItemDecoration(requireContext(), LinearLayoutManager.VERTICAL))
|
|
||||||
return view
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStart() {
|
|
||||||
super.onStart()
|
|
||||||
if (!loaded) {
|
|
||||||
lifecycleScope.launch { init() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
|
||||||
private suspend fun init() {
|
|
||||||
ensureEuiccChannelManager()
|
|
||||||
showProgressBar(-1)
|
|
||||||
val slots = euiccChannelManager.flowEuiccPorts().map { (slotId, portId) ->
|
|
||||||
euiccChannelManager.withEuiccChannel(slotId, portId) { channel ->
|
|
||||||
SlotInfo(
|
|
||||||
channel.logicalSlotId,
|
|
||||||
channel.port.card.isRemovable,
|
|
||||||
channel.port.card.ports.size > 1,
|
|
||||||
channel.portId,
|
|
||||||
channel.lpa.eID,
|
|
||||||
channel.lpa.profiles.find { it.state == LocalProfileInfo.State.Enabled }?.displayName
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}.toList()
|
|
||||||
adapter.slots = slots
|
|
||||||
|
|
||||||
// Ensure we always have a selected slot by default
|
|
||||||
val selectedIdx = slots.indexOfFirst { it.logicalSlotId == state.selectedLogicalSlot }
|
|
||||||
adapter.currentSelectedIdx = if (selectedIdx > 0) {
|
|
||||||
selectedIdx
|
|
||||||
} else {
|
|
||||||
if (slots.isNotEmpty()) {
|
|
||||||
state.selectedLogicalSlot = slots[0].logicalSlotId
|
|
||||||
}
|
|
||||||
0
|
|
||||||
}
|
|
||||||
|
|
||||||
adapter.notifyDataSetChanged()
|
|
||||||
hideProgressBar()
|
|
||||||
loaded = true
|
|
||||||
refreshButtons()
|
|
||||||
}
|
|
||||||
|
|
||||||
private inner class SlotItemHolder(val root: View) : ViewHolder(root) {
|
|
||||||
private val title = root.requireViewById<TextView>(R.id.slot_item_title)
|
|
||||||
private val type = root.requireViewById<TextView>(R.id.slot_item_type)
|
|
||||||
private val eID = root.requireViewById<TextView>(R.id.slot_item_eid)
|
|
||||||
private val activeProfile = root.requireViewById<TextView>(R.id.slot_item_active_profile)
|
|
||||||
private val checkBox = root.requireViewById<CheckBox>(R.id.slot_checkbox)
|
|
||||||
|
|
||||||
private var curIdx = -1
|
|
||||||
|
|
||||||
init {
|
|
||||||
root.setOnClickListener(this::onSelect)
|
|
||||||
checkBox.setOnClickListener(this::onSelect)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("UNUSED_PARAMETER")
|
|
||||||
fun onSelect(view: View) {
|
|
||||||
if (curIdx < 0) return
|
|
||||||
if (adapter.currentSelectedIdx == curIdx) return
|
|
||||||
val lastIdx = adapter.currentSelectedIdx
|
|
||||||
adapter.currentSelectedIdx = curIdx
|
|
||||||
adapter.notifyItemChanged(lastIdx)
|
|
||||||
adapter.notifyItemChanged(curIdx)
|
|
||||||
// Selected index isn't logical slot ID directly, needs a conversion
|
|
||||||
state.selectedLogicalSlot = adapter.slots[adapter.currentSelectedIdx].logicalSlotId
|
|
||||||
}
|
|
||||||
|
|
||||||
fun bind(item: SlotInfo, idx: Int) {
|
|
||||||
curIdx = idx
|
|
||||||
|
|
||||||
type.text = if (item.isRemovable) {
|
|
||||||
root.context.getString(R.string.download_wizard_slot_type_removable)
|
|
||||||
} else if (!item.hasMultiplePorts) {
|
|
||||||
root.context.getString(R.string.download_wizard_slot_type_internal)
|
|
||||||
} else {
|
|
||||||
root.context.getString(
|
|
||||||
R.string.download_wizard_slot_type_internal_port,
|
|
||||||
item.portId
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
title.text = root.context.getString(R.string.download_wizard_slot_title, item.logicalSlotId)
|
|
||||||
eID.text = item.eID
|
|
||||||
activeProfile.text = item.enabledProfileName ?: root.context.getString(R.string.unknown)
|
|
||||||
checkBox.isChecked = adapter.currentSelectedIdx == idx
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private inner class SlotInfoAdapter : RecyclerView.Adapter<SlotItemHolder>() {
|
|
||||||
var slots: List<SlotInfo> = listOf()
|
|
||||||
var currentSelectedIdx = -1
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SlotItemHolder {
|
|
||||||
val root = LayoutInflater.from(parent.context).inflate(R.layout.download_slot_item, parent, false)
|
|
||||||
return SlotItemHolder(root)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemCount(): Int = slots.size
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: SlotItemHolder, position: Int) {
|
|
||||||
holder.bind(slots[position], position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -32,9 +32,9 @@ val <T> T.portId: Int where T: Fragment, T: EuiccChannelFragmentMarker
|
||||||
val <T> T.isUsb: Boolean where T: Fragment, T: EuiccChannelFragmentMarker
|
val <T> T.isUsb: Boolean where T: Fragment, T: EuiccChannelFragmentMarker
|
||||||
get() = requireArguments().getInt("slotId") == EuiccChannelManager.USB_CHANNEL_ID
|
get() = requireArguments().getInt("slotId") == EuiccChannelManager.USB_CHANNEL_ID
|
||||||
|
|
||||||
val <T> T.euiccChannelManager: EuiccChannelManager where T: Fragment, T: OpenEuiccContextMarker
|
val <T> T.euiccChannelManager: EuiccChannelManager where T: Fragment, T: EuiccChannelFragmentMarker
|
||||||
get() = (requireActivity() as BaseEuiccAccessActivity).euiccChannelManager
|
get() = (requireActivity() as BaseEuiccAccessActivity).euiccChannelManager
|
||||||
val <T> T.euiccChannelManagerService: EuiccChannelManagerService where T: Fragment, T: OpenEuiccContextMarker
|
val <T> T.euiccChannelManagerService: EuiccChannelManagerService where T: Fragment, T: EuiccChannelFragmentMarker
|
||||||
get() = (requireActivity() as BaseEuiccAccessActivity).euiccChannelManagerService
|
get() = (requireActivity() as BaseEuiccAccessActivity).euiccChannelManagerService
|
||||||
|
|
||||||
suspend fun <T, R> T.withEuiccChannel(fn: suspend (EuiccChannel) -> R): R where T : Fragment, T : EuiccChannelFragmentMarker {
|
suspend fun <T, R> T.withEuiccChannel(fn: suspend (EuiccChannel) -> R): R where T : Fragment, T : EuiccChannelFragmentMarker {
|
||||||
|
@ -42,7 +42,7 @@ suspend fun <T, R> T.withEuiccChannel(fn: suspend (EuiccChannel) -> R): R where
|
||||||
return euiccChannelManager.withEuiccChannel(slotId, portId, fn)
|
return euiccChannelManager.withEuiccChannel(slotId, portId, fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun <T> T.ensureEuiccChannelManager() where T: Fragment, T: OpenEuiccContextMarker =
|
suspend fun <T> T.ensureEuiccChannelManager() where T: Fragment, T: EuiccChannelFragmentMarker =
|
||||||
(requireActivity() as BaseEuiccAccessActivity).euiccChannelManagerLoaded.await()
|
(requireActivity() as BaseEuiccAccessActivity).euiccChannelManagerLoaded.await()
|
||||||
|
|
||||||
interface EuiccProfilesChangedListener {
|
interface EuiccProfilesChangedListener {
|
||||||
|
|
|
@ -20,19 +20,11 @@ val Fragment.preferenceRepository: PreferenceRepository
|
||||||
get() = requireContext().preferenceRepository
|
get() = requireContext().preferenceRepository
|
||||||
|
|
||||||
object PreferenceKeys {
|
object PreferenceKeys {
|
||||||
// ---- Profile Notifications ----
|
|
||||||
val NOTIFICATION_DOWNLOAD = booleanPreferencesKey("notification_download")
|
val NOTIFICATION_DOWNLOAD = booleanPreferencesKey("notification_download")
|
||||||
val NOTIFICATION_DELETE = booleanPreferencesKey("notification_delete")
|
val NOTIFICATION_DELETE = booleanPreferencesKey("notification_delete")
|
||||||
val NOTIFICATION_SWITCH = booleanPreferencesKey("notification_switch")
|
val NOTIFICATION_SWITCH = booleanPreferencesKey("notification_switch")
|
||||||
|
|
||||||
// ---- Advanced ----
|
|
||||||
val DISABLE_SAFEGUARD_REMOVABLE_ESIM = booleanPreferencesKey("disable_safeguard_removable_esim")
|
val DISABLE_SAFEGUARD_REMOVABLE_ESIM = booleanPreferencesKey("disable_safeguard_removable_esim")
|
||||||
val VERBOSE_LOGGING = booleanPreferencesKey("verbose_logging")
|
val VERBOSE_LOGGING = booleanPreferencesKey("verbose_logging")
|
||||||
|
|
||||||
// ---- Developer Options ----
|
|
||||||
val DEVELOPER_OPTIONS_ENABLED = booleanPreferencesKey("developer_options_enabled")
|
|
||||||
val EXPERIMENTAL_DOWNLOAD_WIZARD = booleanPreferencesKey("experimental_download_wizard")
|
|
||||||
val IGNORE_TLS_CERTIFICATE = booleanPreferencesKey("ignore_tls_certificate")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class PreferenceRepository(context: Context) {
|
class PreferenceRepository(context: Context) {
|
||||||
|
@ -56,16 +48,6 @@ class PreferenceRepository(context: Context) {
|
||||||
val verboseLoggingFlow: Flow<Boolean> =
|
val verboseLoggingFlow: Flow<Boolean> =
|
||||||
dataStore.data.map { it[PreferenceKeys.VERBOSE_LOGGING] ?: false }
|
dataStore.data.map { it[PreferenceKeys.VERBOSE_LOGGING] ?: false }
|
||||||
|
|
||||||
// ---- Developer Options ----
|
|
||||||
val developerOptionsEnabledFlow: Flow<Boolean> =
|
|
||||||
dataStore.data.map { it[PreferenceKeys.DEVELOPER_OPTIONS_ENABLED] ?: false }
|
|
||||||
|
|
||||||
val experimentalDownloadWizardFlow: Flow<Boolean> =
|
|
||||||
dataStore.data.map { it[PreferenceKeys.EXPERIMENTAL_DOWNLOAD_WIZARD] ?: false }
|
|
||||||
|
|
||||||
val ignoreTLSCertificateFlow: Flow<Boolean> =
|
|
||||||
dataStore.data.map { it[PreferenceKeys.IGNORE_TLS_CERTIFICATE] ?: false }
|
|
||||||
|
|
||||||
suspend fun <T> updatePreference(key: Preferences.Key<T>, value: T) {
|
suspend fun <T> updatePreference(key: Preferences.Key<T>, value: T) {
|
||||||
dataStore.edit {
|
dataStore.edit {
|
||||||
it[key] = value
|
it[key] = value
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
|
||||||
|
|
||||||
<path android:fillColor="@android:color/white" android:pathData="M15.41,7.41L14,6l-6,6 6,6 1.41,-1.41L10.83,12z"/>
|
|
||||||
|
|
||||||
</vector>
|
|
|
@ -1,5 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
|
||||||
|
|
||||||
<path android:fillColor="@android:color/white" android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z"/>
|
|
||||||
|
|
||||||
</vector>
|
|
|
@ -1,74 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:id="@+id/step_fragment_container"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:layout_constraintBottom_toTopOf="@id/download_wizard_navigation"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent" />
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:id="@+id/guideline"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:visibility="invisible"
|
|
||||||
app:layout_constraintBottom_toTopOf="@id/download_wizard_navigation"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent" />
|
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/progress"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:indeterminate="true"
|
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/guideline"
|
|
||||||
app:layout_constraintBottom_toTopOf="@id/download_wizard_navigation"
|
|
||||||
style="@style/Widget.AppCompat.ProgressBar.Horizontal" />
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
android:id="@+id/download_wizard_navigation"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:background="?attr/colorSurfaceContainer"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent">
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
|
||||||
android:id="@+id/download_wizard_back"
|
|
||||||
android:text="@string/download_wizard_back"
|
|
||||||
android:background="?attr/selectableItemBackground"
|
|
||||||
android:textColor="?attr/colorPrimary"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
app:icon="@drawable/ic_chevron_left"
|
|
||||||
app:iconGravity="start"
|
|
||||||
app:iconTint="?attr/colorPrimary"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
|
||||||
android:id="@+id/download_wizard_next"
|
|
||||||
android:text="@string/download_wizard_next"
|
|
||||||
android:background="?attr/selectableItemBackground"
|
|
||||||
android:textColor="?attr/colorPrimary"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
app:icon="@drawable/ic_chevron_right"
|
|
||||||
app:iconGravity="end"
|
|
||||||
app:iconTint="?attr/colorPrimary"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
|
@ -1,94 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingBottom="20sp"
|
|
||||||
android:paddingTop="10sp"
|
|
||||||
android:paddingStart="20sp"
|
|
||||||
android:paddingEnd="20sp"
|
|
||||||
android:background="?attr/selectableItemBackground">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/slot_item_title"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="10sp"
|
|
||||||
android:textSize="18sp"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/slot_item_type_label"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:minWidth="100dp"
|
|
||||||
android:text="@string/download_wizard_slot_type"
|
|
||||||
android:textSize="14sp" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/slot_item_type"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textSize="14sp" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/slot_item_eid_label"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:minWidth="100dp"
|
|
||||||
android:text="@string/download_wizard_slot_eid"
|
|
||||||
android:textSize="14sp" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/slot_item_eid"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textSize="14sp" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/slot_item_active_profile_label"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:minWidth="100dp"
|
|
||||||
android:text="@string/download_wizard_slot_active_profile"
|
|
||||||
android:textSize="14sp" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/slot_item_active_profile"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textSize="14sp" />
|
|
||||||
|
|
||||||
<androidx.constraintlayout.helper.widget.Flow
|
|
||||||
android:id="@+id/flow1"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="10sp"
|
|
||||||
android:layout_marginTop="20sp"
|
|
||||||
android:layout_marginEnd="10sp"
|
|
||||||
app:constraint_referenced_ids="slot_item_type_label,slot_item_type,slot_item_eid_label,slot_item_eid,slot_item_active_profile_label,slot_item_active_profile"
|
|
||||||
app:flow_wrapMode="aligned"
|
|
||||||
app:flow_horizontalAlign="start"
|
|
||||||
app:flow_horizontalBias="1"
|
|
||||||
app:flow_horizontalGap="10sp"
|
|
||||||
app:flow_horizontalStyle="packed"
|
|
||||||
app:flow_maxElementsWrap="2"
|
|
||||||
app:flow_verticalBias="0"
|
|
||||||
app:flow_verticalGap="16sp"
|
|
||||||
app:flow_verticalStyle="packed"
|
|
||||||
app:layout_constraintEnd_toStartOf="@id/slot_checkbox"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/slot_item_title" />
|
|
||||||
|
|
||||||
<CheckBox
|
|
||||||
android:id="@+id/slot_checkbox"
|
|
||||||
android:layout_width="48dp"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toEndOf="@id/flow1"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
|
@ -1,28 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/download_slot_select_title"
|
|
||||||
android:text="@string/download_wizard_slot_select"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textSize="20sp"
|
|
||||||
android:layout_margin="20sp"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/download_slot_list"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/download_slot_select_title"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constrainedHeight="true" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
|
@ -58,18 +58,6 @@
|
||||||
<string name="profile_download_low_nvram_title">This download may fail</string>
|
<string name="profile_download_low_nvram_title">This download may fail</string>
|
||||||
<string name="profile_download_low_nvram_message">This download may fail due to low remaining capacity.</string>
|
<string name="profile_download_low_nvram_message">This download may fail due to low remaining capacity.</string>
|
||||||
|
|
||||||
<string name="download_wizard">Download Wizard</string>
|
|
||||||
<string name="download_wizard_back">Back</string>
|
|
||||||
<string name="download_wizard_next">Next</string>
|
|
||||||
<string name="download_wizard_slot_select">Confirm the eSIM slot:</string>
|
|
||||||
<string name="download_wizard_slot_title">Logical slot %d</string>
|
|
||||||
<string name="download_wizard_slot_type">Type:</string>
|
|
||||||
<string name="download_wizard_slot_type_removable">Removable</string>
|
|
||||||
<string name="download_wizard_slot_type_internal">Internal</string>
|
|
||||||
<string name="download_wizard_slot_type_internal_port">Internal, port %d</string>
|
|
||||||
<string name="download_wizard_slot_eid">eID:</string>
|
|
||||||
<string name="download_wizard_slot_active_profile">Active Profile:</string>
|
|
||||||
|
|
||||||
<string name="profile_rename_new_name">New nickname</string>
|
<string name="profile_rename_new_name">New nickname</string>
|
||||||
|
|
||||||
<string name="profile_delete_confirm">Are you sure you want to delete the profile %s? This operation is irreversible.</string>
|
<string name="profile_delete_confirm">Are you sure you want to delete the profile %s? This operation is irreversible.</string>
|
||||||
|
@ -109,9 +97,6 @@
|
||||||
<string name="logs_save">Save</string>
|
<string name="logs_save">Save</string>
|
||||||
<string name="logs_filename_template">Logs at %s</string>
|
<string name="logs_filename_template">Logs at %s</string>
|
||||||
|
|
||||||
<string name="developer_options_steps">You are %d steps away from being a developer.</string>
|
|
||||||
<string name="developer_options_enabled">You are now a developer!</string>
|
|
||||||
|
|
||||||
<string name="pref_settings">Settings</string>
|
<string name="pref_settings">Settings</string>
|
||||||
<string name="pref_notifications">Notifications</string>
|
<string name="pref_notifications">Notifications</string>
|
||||||
<string name="pref_notifications_desc">eSIM profile operations send notifications to the carrier. Fine-tune this behavior as needed here.</string>
|
<string name="pref_notifications_desc">eSIM profile operations send notifications to the carrier. Fine-tune this behavior as needed here.</string>
|
||||||
|
@ -128,11 +113,6 @@
|
||||||
<string name="pref_advanced_verbose_logging_desc">Enable verbose logs, which may contain sensitive information. Only share your logs with someone you trust after turning this on.</string>
|
<string name="pref_advanced_verbose_logging_desc">Enable verbose logs, which may contain sensitive information. Only share your logs with someone you trust after turning this on.</string>
|
||||||
<string name="pref_advanced_logs">Logs</string>
|
<string name="pref_advanced_logs">Logs</string>
|
||||||
<string name="pref_advanced_logs_desc">View recent debug logs of the application</string>
|
<string name="pref_advanced_logs_desc">View recent debug logs of the application</string>
|
||||||
<string name="pref_developer">Developer Options</string>
|
|
||||||
<string name="pref_developer_experimental_download_wizard">Experimental Download Wizard</string>
|
|
||||||
<string name="pref_developer_experimental_download_wizard_desc">Enable the experimental new download wizard. Note that it is not fully working yet.</string>
|
|
||||||
<string name="pref_developer_ignore_tls_certificate">Ignore SM-DP+ TLS certificate</string>
|
|
||||||
<string name="pref_developer_ignore_tls_certificate_desc">Ignore SM-DP+ TLS certificate, allow any RSP</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>
|
||||||
|
|
|
@ -41,26 +41,6 @@
|
||||||
app:iconSpaceReserved="false"
|
app:iconSpaceReserved="false"
|
||||||
app:title="@string/pref_advanced_logs"
|
app:title="@string/pref_advanced_logs"
|
||||||
app:summary="@string/pref_advanced_logs_desc" />
|
app:summary="@string/pref_advanced_logs_desc" />
|
||||||
|
|
||||||
</PreferenceCategory>
|
|
||||||
|
|
||||||
<PreferenceCategory
|
|
||||||
app:key="pref_developer"
|
|
||||||
app:title="@string/pref_developer"
|
|
||||||
app:iconSpaceReserved="false">
|
|
||||||
|
|
||||||
<CheckBoxPreference
|
|
||||||
app:key="pref_developer_experimental_download_wizard"
|
|
||||||
app:iconSpaceReserved="false"
|
|
||||||
app:title="@string/pref_developer_experimental_download_wizard"
|
|
||||||
app:summary="@string/pref_developer_experimental_download_wizard_desc" />
|
|
||||||
|
|
||||||
<CheckBoxPreference
|
|
||||||
app:iconSpaceReserved="false"
|
|
||||||
app:key="pref_developer_ignore_tls_certificate"
|
|
||||||
app:summary="@string/pref_developer_ignore_tls_certificate_desc"
|
|
||||||
app:title="@string/pref_developer_ignore_tls_certificate" />
|
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
|
|
|
@ -35,8 +35,7 @@ class PrivilegedEuiccChannelFactory(context: Context) : DefaultEuiccChannelFacto
|
||||||
tm,
|
tm,
|
||||||
context.preferenceRepository.verboseLoggingFlow
|
context.preferenceRepository.verboseLoggingFlow
|
||||||
),
|
),
|
||||||
context.preferenceRepository.verboseLoggingFlow,
|
context.preferenceRepository.verboseLoggingFlow
|
||||||
context.preferenceRepository.ignoreTLSCertificateFlow,
|
|
||||||
)
|
)
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
// Failed
|
// Failed
|
||||||
|
|
|
@ -9,14 +9,10 @@ import java.net.URL
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
import javax.net.ssl.HttpsURLConnection
|
import javax.net.ssl.HttpsURLConnection
|
||||||
import javax.net.ssl.SSLContext
|
import javax.net.ssl.SSLContext
|
||||||
import javax.net.ssl.SSLSocketFactory
|
|
||||||
import javax.net.ssl.TrustManager
|
import javax.net.ssl.TrustManager
|
||||||
import javax.net.ssl.TrustManagerFactory
|
import javax.net.ssl.TrustManagerFactory
|
||||||
|
|
||||||
class HttpInterfaceImpl(
|
class HttpInterfaceImpl(private val verboseLoggingFlow: Flow<Boolean>) : HttpInterface {
|
||||||
private val verboseLoggingFlow: Flow<Boolean>,
|
|
||||||
private val ignoreTLSCertificateFlow: Flow<Boolean>
|
|
||||||
) : HttpInterface {
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "HttpInterfaceImpl"
|
private const val TAG = "HttpInterfaceImpl"
|
||||||
}
|
}
|
||||||
|
@ -40,6 +36,9 @@ class HttpInterfaceImpl(
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
val sslContext = SSLContext.getInstance("TLS")
|
||||||
|
sslContext.init(null, trustManagers, SecureRandom())
|
||||||
|
|
||||||
val conn = parsedUrl.openConnection() as HttpsURLConnection
|
val conn = parsedUrl.openConnection() as HttpsURLConnection
|
||||||
conn.connectTimeout = 2000
|
conn.connectTimeout = 2000
|
||||||
|
|
||||||
|
@ -48,7 +47,7 @@ class HttpInterfaceImpl(
|
||||||
conn.readTimeout = 1000
|
conn.readTimeout = 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
conn.sslSocketFactory = getSocketFactory()
|
conn.sslSocketFactory = sslContext.socketFactory
|
||||||
conn.requestMethod = "POST"
|
conn.requestMethod = "POST"
|
||||||
conn.doInput = true
|
conn.doInput = true
|
||||||
conn.doOutput = true
|
conn.doOutput = true
|
||||||
|
@ -80,18 +79,6 @@ class HttpInterfaceImpl(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getSocketFactory(): SSLSocketFactory {
|
|
||||||
val trustManagers =
|
|
||||||
if (runBlocking { ignoreTLSCertificateFlow.first() }) {
|
|
||||||
arrayOf(IgnoreTLSCertificate())
|
|
||||||
} else {
|
|
||||||
this.trustManagers
|
|
||||||
}
|
|
||||||
val sslContext = SSLContext.getInstance("TLS")
|
|
||||||
sslContext.init(null, trustManagers, SecureRandom())
|
|
||||||
return sslContext.socketFactory
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun usePublicKeyIds(pkids: Array<String>) {
|
override fun usePublicKeyIds(pkids: Array<String>) {
|
||||||
val trustManagerFactory = TrustManagerFactory.getInstance("PKIX").apply {
|
val trustManagerFactory = TrustManagerFactory.getInstance("PKIX").apply {
|
||||||
init(keyIdToKeystore(pkids))
|
init(keyIdToKeystore(pkids))
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
package net.typeblog.lpac_jni.impl
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import java.security.cert.X509Certificate
|
|
||||||
import javax.net.ssl.X509TrustManager
|
|
||||||
|
|
||||||
@SuppressLint("CustomX509TrustManager")
|
|
||||||
class IgnoreTLSCertificate : X509TrustManager {
|
|
||||||
@SuppressLint("TrustAllX509TrustManager")
|
|
||||||
override fun checkClientTrusted(p0: Array<out X509Certificate>?, p1: String?) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("TrustAllX509TrustManager")
|
|
||||||
override fun checkServerTrusted(p0: Array<out X509Certificate>?, p1: String?) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getAcceptedIssuers(): Array<X509Certificate> {
|
|
||||||
return emptyArray()
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue