forked from PeterCxy/OpenEUICC
Compare commits
12 commits
e9f4d3d1f9
...
c4b513fc0a
Author | SHA1 | Date | |
---|---|---|---|
c4b513fc0a | |||
6458f54db2 | |||
87f36f4166 | |||
4fb59a4b01 | |||
16636988b0 | |||
93e7297caa | |||
1087a676d4 | |||
375d13b7c4 | |||
a3d59a0761 | |||
5f0dbe3098 | |||
efa9b8bfa4 | |||
47d5c3881c |
19 changed files with 698 additions and 19 deletions
|
@ -32,6 +32,10 @@
|
||||||
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,7 +42,8 @@ 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)
|
||||||
|
@ -72,7 +73,8 @@ 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,14 +11,15 @@ 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))
|
LocalProfileAssistantImpl(apduInterface, HttpInterfaceImpl(verboseLoggingFlow, ignoreTLSCertificateFlow))
|
||||||
|
|
||||||
override val valid: Boolean
|
override val valid: Boolean
|
||||||
get() = lpa.valid
|
get() = lpa.valid
|
||||||
|
|
|
@ -31,10 +31,12 @@ 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
|
||||||
|
@ -105,10 +107,19 @@ 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,26 +3,48 @@ 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.view.LayoutInflater
|
import android.widget.Toast
|
||||||
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")
|
||||||
?.summary = requireContext().selfAppVersion
|
?.apply {
|
||||||
|
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 {
|
||||||
|
@ -50,6 +72,12 @@ 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() {
|
||||||
|
@ -57,6 +85,44 @@ 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 }
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,165 @@
|
||||||
|
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: EuiccChannelFragmentMarker
|
val <T> T.euiccChannelManager: EuiccChannelManager where T: Fragment, T: OpenEuiccContextMarker
|
||||||
get() = (requireActivity() as BaseEuiccAccessActivity).euiccChannelManager
|
get() = (requireActivity() as BaseEuiccAccessActivity).euiccChannelManager
|
||||||
val <T> T.euiccChannelManagerService: EuiccChannelManagerService where T: Fragment, T: EuiccChannelFragmentMarker
|
val <T> T.euiccChannelManagerService: EuiccChannelManagerService where T: Fragment, T: OpenEuiccContextMarker
|
||||||
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: EuiccChannelFragmentMarker =
|
suspend fun <T> T.ensureEuiccChannelManager() where T: Fragment, T: OpenEuiccContextMarker =
|
||||||
(requireActivity() as BaseEuiccAccessActivity).euiccChannelManagerLoaded.await()
|
(requireActivity() as BaseEuiccAccessActivity).euiccChannelManagerLoaded.await()
|
||||||
|
|
||||||
interface EuiccProfilesChangedListener {
|
interface EuiccProfilesChangedListener {
|
||||||
|
|
|
@ -20,11 +20,19 @@ 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) {
|
||||||
|
@ -48,6 +56,16 @@ 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
|
||||||
|
|
5
app-common/src/main/res/drawable/ic_chevron_left.xml
Normal file
5
app-common/src/main/res/drawable/ic_chevron_left.xml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<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>
|
5
app-common/src/main/res/drawable/ic_chevron_right.xml
Normal file
5
app-common/src/main/res/drawable/ic_chevron_right.xml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<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>
|
74
app-common/src/main/res/layout/activity_download_wizard.xml
Normal file
74
app-common/src/main/res/layout/activity_download_wizard.xml
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
<?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>
|
94
app-common/src/main/res/layout/download_slot_item.xml
Normal file
94
app-common/src/main/res/layout/download_slot_item.xml
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
<?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>
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?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,6 +58,18 @@
|
||||||
<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>
|
||||||
|
@ -97,6 +109,9 @@
|
||||||
<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>
|
||||||
|
@ -113,6 +128,11 @@
|
||||||
<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,6 +41,26 @@
|
||||||
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,7 +35,8 @@ 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,10 +9,14 @@ 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(private val verboseLoggingFlow: Flow<Boolean>) : HttpInterface {
|
class HttpInterfaceImpl(
|
||||||
|
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"
|
||||||
}
|
}
|
||||||
|
@ -36,9 +40,6 @@ class HttpInterfaceImpl(private val verboseLoggingFlow: Flow<Boolean>) : HttpInt
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
@ -47,7 +48,7 @@ class HttpInterfaceImpl(private val verboseLoggingFlow: Flow<Boolean>) : HttpInt
|
||||||
conn.readTimeout = 1000
|
conn.readTimeout = 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
conn.sslSocketFactory = sslContext.socketFactory
|
conn.sslSocketFactory = getSocketFactory()
|
||||||
conn.requestMethod = "POST"
|
conn.requestMethod = "POST"
|
||||||
conn.doInput = true
|
conn.doInput = true
|
||||||
conn.doOutput = true
|
conn.doOutput = true
|
||||||
|
@ -79,6 +80,18 @@ class HttpInterfaceImpl(private val verboseLoggingFlow: Flow<Boolean>) : HttpInt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
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