refactor: Remove all usage of knownChannels
Some checks failed
/ build-debug (push) Has been cancelled
/ release (push) Failing after 5m29s

enumerateEuiccChannels() should return all discovered channels on its
own. Outside classes should never access the cached open channels
directly.
This commit is contained in:
Peter Cai 2024-05-09 16:08:00 -04:00
parent 5785fe2e7c
commit 1a22854d05
6 changed files with 66 additions and 46 deletions

View file

@ -21,7 +21,7 @@ open class DefaultEuiccChannelManager(
const val TAG = "EuiccChannelManager" const val TAG = "EuiccChannelManager"
} }
private val channels = mutableListOf<EuiccChannel>() private val channelCache = mutableListOf<EuiccChannel>()
private val lock = Mutex() private val lock = Mutex()
@ -39,13 +39,13 @@ open class DefaultEuiccChannelManager(
private suspend fun tryOpenEuiccChannel(port: UiccPortInfoCompat): EuiccChannel? { private suspend fun tryOpenEuiccChannel(port: UiccPortInfoCompat): EuiccChannel? {
lock.withLock { lock.withLock {
val existing = val existing =
channels.find { it.slotId == port.card.physicalSlotIndex && it.portId == port.portIndex } channelCache.find { it.slotId == port.card.physicalSlotIndex && it.portId == port.portIndex }
if (existing != null) { if (existing != null) {
if (existing.valid && port.logicalSlotIndex == existing.logicalSlotId) { if (existing.valid && port.logicalSlotIndex == existing.logicalSlotId) {
return existing return existing
} else { } else {
existing.close() existing.close()
channels.remove(existing) channelCache.remove(existing)
} }
} }
@ -57,7 +57,7 @@ open class DefaultEuiccChannelManager(
val channel = euiccChannelFactory.tryOpenEuiccChannel(port) ?: return null val channel = euiccChannelFactory.tryOpenEuiccChannel(port) ?: return null
if (channel.valid) { if (channel.valid) {
channels.add(channel) channelCache.add(channel)
return channel return channel
} else { } else {
Log.i( Log.i(
@ -128,7 +128,7 @@ open class DefaultEuiccChannelManager(
override suspend fun waitForReconnect(physicalSlotId: Int, portId: Int, timeoutMillis: Long) { override suspend fun waitForReconnect(physicalSlotId: Int, portId: Int, timeoutMillis: Long) {
// If there is already a valid channel, we close it proactively // If there is already a valid channel, we close it proactively
// Sometimes the current channel can linger on for a bit even after it should have become invalid // Sometimes the current channel can linger on for a bit even after it should have become invalid
channels.find { it.slotId == physicalSlotId && it.portId == portId }?.apply { channelCache.find { it.slotId == physicalSlotId && it.portId == portId }?.apply {
if (valid) close() if (valid) close()
} }
@ -148,30 +148,26 @@ open class DefaultEuiccChannelManager(
} }
} }
override suspend fun enumerateEuiccChannels() { override suspend fun enumerateEuiccChannels(): List<EuiccChannel> =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
for (uiccInfo in uiccCards) { uiccCards.flatMap { info ->
for (port in uiccInfo.ports) { info.ports.mapNotNull { port ->
if (tryOpenEuiccChannel(port) != null) { tryOpenEuiccChannel(port)?.also {
Log.d( Log.d(
TAG, TAG,
"Found eUICC on slot ${uiccInfo.physicalSlotIndex} port ${port.portIndex}" "Found eUICC on slot ${info.physicalSlotIndex} port ${port.portIndex}"
) )
} }
} }
} }
} }
}
override val knownChannels: List<EuiccChannel>
get() = channels.toList()
override fun invalidate() { override fun invalidate() {
for (channel in channels) { for (channel in channelCache) {
channel.close() channel.close()
} }
channels.clear() channelCache.clear()
euiccChannelFactory.cleanup() euiccChannelFactory.cleanup()
} }
} }

View file

@ -1,12 +1,22 @@
package im.angry.openeuicc.core package im.angry.openeuicc.core
/**
* EuiccChannelManager holds references to, and manages the lifecycles of, individual
* APDU channels to SIM cards. The find* methods will create channels when needed, and
* all opened channels will be held in an internal cache until invalidate() is called
* or when this instance is destroyed.
*
* To precisely control the lifecycle of this object itself (and thus its cached channels),
* all other compoents must access EuiccChannelManager objects through EuiccChannelManagerService.
* Holding references independent of EuiccChannelManagerService is unsupported.
*/
interface EuiccChannelManager { interface EuiccChannelManager {
val knownChannels: List<EuiccChannel>
/** /**
* Scan all possible sources for EuiccChannels and have them cached for future use * Scan all possible sources for EuiccChannels, return them and have all
* scanned channels cached; these channels will remain open for the entire lifetime of
* this EuiccChannelManager object, unless disconnected externally or invalidate()'d
*/ */
suspend fun enumerateEuiccChannels() suspend fun enumerateEuiccChannels(): List<EuiccChannel>
/** /**
* Wait for a slot + port to reconnect (i.e. become valid again) * Wait for a slot + port to reconnect (i.e. become valid again)
@ -41,7 +51,7 @@ interface EuiccChannelManager {
fun findEuiccChannelByPortBlocking(physicalSlotId: Int, portId: Int): EuiccChannel? fun findEuiccChannelByPortBlocking(physicalSlotId: Int, portId: Int): EuiccChannel?
/** /**
* Invalidate all EuiccChannels previously known by this Manager * Invalidate all EuiccChannels previously cached by this Manager
*/ */
fun invalidate() fun invalidate()

View file

@ -9,23 +9,23 @@ import kotlinx.coroutines.withContext
class DirectProfileDownloadActivity : BaseEuiccAccessActivity(), SlotSelectFragment.SlotSelectedListener, OpenEuiccContextMarker { class DirectProfileDownloadActivity : BaseEuiccAccessActivity(), SlotSelectFragment.SlotSelectedListener, OpenEuiccContextMarker {
override fun onInit() { override fun onInit() {
lifecycleScope.launch { lifecycleScope.launch {
withContext(Dispatchers.IO) { val knownChannels = withContext(Dispatchers.IO) {
euiccChannelManager.enumerateEuiccChannels() euiccChannelManager.enumerateEuiccChannels()
} }
when { when {
euiccChannelManager.knownChannels.isEmpty() -> { knownChannels.isEmpty() -> {
finish() finish()
} }
euiccChannelManager.knownChannels.hasMultipleChips -> { knownChannels.hasMultipleChips -> {
SlotSelectFragment.newInstance() SlotSelectFragment.newInstance(knownChannels.sortedBy { it.logicalSlotId })
.show(supportFragmentManager, SlotSelectFragment.TAG) .show(supportFragmentManager, SlotSelectFragment.TAG)
} }
else -> { else -> {
// If the device has only one eSIM "chip" (but may be mapped to multiple slots), // If the device has only one eSIM "chip" (but may be mapped to multiple slots),
// we can skip the slot selection dialog since there is only one chip to save to. // we can skip the slot selection dialog since there is only one chip to save to.
onSlotSelected(euiccChannelManager.knownChannels[0].slotId, onSlotSelected(knownChannels[0].slotId,
euiccChannelManager.knownChannels[0].portId) knownChannels[0].portId)
} }
} }
} }

View file

@ -95,9 +95,8 @@ open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
} }
private suspend fun init() { private suspend fun init() {
withContext(Dispatchers.IO) { val knownChannels = withContext(Dispatchers.IO) {
euiccChannelManager.enumerateEuiccChannels() euiccChannelManager.enumerateEuiccChannels().onEach {
euiccChannelManager.knownChannels.forEach {
Log.d(TAG, "slot ${it.slotId} port ${it.portId}") Log.d(TAG, "slot ${it.slotId} port ${it.portId}")
Log.d(TAG, it.lpa.eID) Log.d(TAG, it.lpa.eID)
// Request the system to refresh the list of profiles every time we start // Request the system to refresh the list of profiles every time we start
@ -108,7 +107,7 @@ open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
} }
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
euiccChannelManager.knownChannels.sortedBy { it.logicalSlotId }.forEach { channel -> knownChannels.sortedBy { it.logicalSlotId }.forEach { channel ->
spinnerAdapter.add(getString(R.string.channel_name_format, channel.logicalSlotId)) spinnerAdapter.add(getString(R.string.channel_name_format, channel.logicalSlotId))
fragments.add(appContainer.uiComponentFactory.createEuiccManagementFragment(channel)) fragments.add(appContainer.uiComponentFactory.createEuiccManagementFragment(channel))
} }

View file

@ -16,8 +16,14 @@ class SlotSelectFragment : BaseMaterialDialogFragment(), OpenEuiccContextMarker
companion object { companion object {
const val TAG = "SlotSelectFragment" const val TAG = "SlotSelectFragment"
fun newInstance(): SlotSelectFragment { fun newInstance(knownChannels: List<EuiccChannel>): SlotSelectFragment {
return SlotSelectFragment() return SlotSelectFragment().apply {
arguments = Bundle().apply {
putIntArray("slotIds", knownChannels.map { it.slotId }.toIntArray())
putIntArray("logicalSlotIds", knownChannels.map { it.logicalSlotId }.toIntArray())
putIntArray("portIds", knownChannels.map { it.portId }.toIntArray())
}
}
} }
} }
@ -28,10 +34,10 @@ class SlotSelectFragment : BaseMaterialDialogFragment(), OpenEuiccContextMarker
private lateinit var toolbar: Toolbar private lateinit var toolbar: Toolbar
private lateinit var spinner: Spinner private lateinit var spinner: Spinner
private val channels: List<EuiccChannel> by lazy { private lateinit var adapter: ArrayAdapter<String>
(requireActivity() as BaseEuiccAccessActivity).euiccChannelManager private lateinit var slotIds: IntArray
.knownChannels.sortedBy { it.logicalSlotId } private lateinit var logicalSlotIds: IntArray
} private lateinit var portIds: IntArray
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
@ -44,26 +50,35 @@ class SlotSelectFragment : BaseMaterialDialogFragment(), OpenEuiccContextMarker
toolbar.setTitle(R.string.slot_select) toolbar.setTitle(R.string.slot_select)
toolbar.inflateMenu(R.menu.fragment_slot_select) toolbar.inflateMenu(R.menu.fragment_slot_select)
val adapter = ArrayAdapter<String>(inflater.context, R.layout.spinner_item) adapter = ArrayAdapter<String>(inflater.context, R.layout.spinner_item)
spinner = view.requireViewById(R.id.spinner) spinner = view.requireViewById(R.id.spinner)
spinner.adapter = adapter spinner.adapter = adapter
channels.forEach { channel -> return view
adapter.add(getString(R.string.channel_name_format, channel.logicalSlotId)) }
override fun onStart() {
super.onStart()
slotIds = requireArguments().getIntArray("slotIds")!!
logicalSlotIds = requireArguments().getIntArray("logicalSlotIds")!!
portIds = requireArguments().getIntArray("portIds")!!
logicalSlotIds.forEach { id ->
adapter.add(getString(R.string.channel_name_format, id))
} }
toolbar.setNavigationOnClickListener { toolbar.setNavigationOnClickListener {
(requireActivity() as SlotSelectedListener).onSlotSelectCancelled() (requireActivity() as SlotSelectedListener).onSlotSelectCancelled()
} }
toolbar.setOnMenuItemClickListener { toolbar.setOnMenuItemClickListener {
val channel = channels[spinner.selectedItemPosition] val slotId = slotIds[spinner.selectedItemPosition]
(requireActivity() as SlotSelectedListener).onSlotSelected(channel.slotId, channel.portId) val portId = portIds[spinner.selectedItemPosition]
(requireActivity() as SlotSelectedListener).onSlotSelected(slotId, portId)
dismiss() dismiss()
true true
} }
return view
} }
override fun onResume() { override fun onResume() {

View file

@ -15,12 +15,12 @@ val TelephonyManager.dsdsEnabled: Boolean
get() = activeModemCount >= 2 get() = activeModemCount >= 2
fun TelephonyManager.setDsdsEnabled(euiccManager: EuiccChannelManager, enabled: Boolean) { fun TelephonyManager.setDsdsEnabled(euiccManager: EuiccChannelManager, enabled: Boolean) {
runBlocking { val knownChannels = runBlocking {
euiccManager.enumerateEuiccChannels() euiccManager.enumerateEuiccChannels()
} }
// Disable all eSIM profiles before performing a DSDS switch (only for internal eSIMs) // Disable all eSIM profiles before performing a DSDS switch (only for internal eSIMs)
euiccManager.knownChannels.forEach { knownChannels.forEach {
if (!it.removable) { if (!it.removable) {
it.lpa.disableActiveProfileWithUndo() it.lpa.disableActiveProfileWithUndo()
} }