ソースを参照

refactor: Remove all usage of knownChannels

enumerateEuiccChannels() should return all discovered channels on its
own. Outside classes should never access the cached open channels
directly.
Peter Cai 1 年間 前
コミット
1a22854d05

+ 12 - 16
app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelManager.kt

@@ -21,7 +21,7 @@ open class DefaultEuiccChannelManager(
         const val TAG = "EuiccChannelManager"
     }
 
-    private val channels = mutableListOf<EuiccChannel>()
+    private val channelCache = mutableListOf<EuiccChannel>()
 
     private val lock = Mutex()
 
@@ -39,13 +39,13 @@ open class DefaultEuiccChannelManager(
     private suspend fun tryOpenEuiccChannel(port: UiccPortInfoCompat): EuiccChannel? {
         lock.withLock {
             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.valid && port.logicalSlotIndex == existing.logicalSlotId) {
                     return existing
                 } else {
                     existing.close()
-                    channels.remove(existing)
+                    channelCache.remove(existing)
                 }
             }
 
@@ -57,7 +57,7 @@ open class DefaultEuiccChannelManager(
             val channel = euiccChannelFactory.tryOpenEuiccChannel(port) ?: return null
 
             if (channel.valid) {
-                channels.add(channel)
+                channelCache.add(channel)
                 return channel
             } else {
                 Log.i(
@@ -128,7 +128,7 @@ open class DefaultEuiccChannelManager(
     override suspend fun waitForReconnect(physicalSlotId: Int, portId: Int, timeoutMillis: Long) {
         // 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
-        channels.find { it.slotId == physicalSlotId && it.portId == portId }?.apply {
+        channelCache.find { it.slotId == physicalSlotId && it.portId == portId }?.apply {
             if (valid) close()
         }
 
@@ -148,30 +148,26 @@ open class DefaultEuiccChannelManager(
         }
     }
 
-    override suspend fun enumerateEuiccChannels() {
+    override suspend fun enumerateEuiccChannels(): List<EuiccChannel> =
         withContext(Dispatchers.IO) {
-            for (uiccInfo in uiccCards) {
-                for (port in uiccInfo.ports) {
-                    if (tryOpenEuiccChannel(port) != null) {
+            uiccCards.flatMap { info ->
+                info.ports.mapNotNull { port ->
+                    tryOpenEuiccChannel(port)?.also {
                         Log.d(
                             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() {
-        for (channel in channels) {
+        for (channel in channelCache) {
             channel.close()
         }
 
-        channels.clear()
+        channelCache.clear()
         euiccChannelFactory.cleanup()
     }
 }

+ 15 - 5
app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelManager.kt

@@ -1,12 +1,22 @@
 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 {
-    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)
@@ -41,7 +51,7 @@ interface EuiccChannelManager {
     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()
 

+ 6 - 6
app-common/src/main/java/im/angry/openeuicc/ui/DirectProfileDownloadActivity.kt

@@ -9,23 +9,23 @@ import kotlinx.coroutines.withContext
 class DirectProfileDownloadActivity : BaseEuiccAccessActivity(), SlotSelectFragment.SlotSelectedListener, OpenEuiccContextMarker {
     override fun onInit() {
         lifecycleScope.launch {
-            withContext(Dispatchers.IO) {
+            val knownChannels = withContext(Dispatchers.IO) {
                 euiccChannelManager.enumerateEuiccChannels()
             }
 
             when {
-                euiccChannelManager.knownChannels.isEmpty() -> {
+                knownChannels.isEmpty() -> {
                     finish()
                 }
-                euiccChannelManager.knownChannels.hasMultipleChips -> {
-                    SlotSelectFragment.newInstance()
+                knownChannels.hasMultipleChips -> {
+                    SlotSelectFragment.newInstance(knownChannels.sortedBy { it.logicalSlotId })
                         .show(supportFragmentManager, SlotSelectFragment.TAG)
                 }
                 else -> {
                     // 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.
-                    onSlotSelected(euiccChannelManager.knownChannels[0].slotId,
-                        euiccChannelManager.knownChannels[0].portId)
+                    onSlotSelected(knownChannels[0].slotId,
+                        knownChannels[0].portId)
                 }
             }
         }

+ 3 - 4
app-common/src/main/java/im/angry/openeuicc/ui/MainActivity.kt

@@ -95,9 +95,8 @@ open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
     }
 
     private suspend fun init() {
-        withContext(Dispatchers.IO) {
-            euiccChannelManager.enumerateEuiccChannels()
-            euiccChannelManager.knownChannels.forEach {
+        val knownChannels = withContext(Dispatchers.IO) {
+            euiccChannelManager.enumerateEuiccChannels().onEach {
                 Log.d(TAG, "slot ${it.slotId} port ${it.portId}")
                 Log.d(TAG, it.lpa.eID)
                 // 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) {
-            euiccChannelManager.knownChannels.sortedBy { it.logicalSlotId }.forEach { channel ->
+            knownChannels.sortedBy { it.logicalSlotId }.forEach { channel ->
                 spinnerAdapter.add(getString(R.string.channel_name_format, channel.logicalSlotId))
                 fragments.add(appContainer.uiComponentFactory.createEuiccManagementFragment(channel))
             }

+ 28 - 13
app-common/src/main/java/im/angry/openeuicc/ui/SlotSelectFragment.kt

@@ -16,8 +16,14 @@ class SlotSelectFragment : BaseMaterialDialogFragment(), OpenEuiccContextMarker
     companion object {
         const val TAG = "SlotSelectFragment"
 
-        fun newInstance(): SlotSelectFragment {
-            return SlotSelectFragment()
+        fun newInstance(knownChannels: List<EuiccChannel>): 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 spinner: Spinner
-    private val channels: List<EuiccChannel> by lazy {
-        (requireActivity() as BaseEuiccAccessActivity).euiccChannelManager
-            .knownChannels.sortedBy { it.logicalSlotId }
-    }
+    private lateinit var adapter: ArrayAdapter<String>
+    private lateinit var slotIds: IntArray
+    private lateinit var logicalSlotIds: IntArray
+    private lateinit var portIds: IntArray
 
     override fun onCreateView(
         inflater: LayoutInflater,
@@ -44,26 +50,35 @@ class SlotSelectFragment : BaseMaterialDialogFragment(), OpenEuiccContextMarker
         toolbar.setTitle(R.string.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.adapter = adapter
 
-        channels.forEach { channel ->
-            adapter.add(getString(R.string.channel_name_format, channel.logicalSlotId))
+        return view
+    }
+
+    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 {
             (requireActivity() as SlotSelectedListener).onSlotSelectCancelled()
         }
         toolbar.setOnMenuItemClickListener {
-            val channel = channels[spinner.selectedItemPosition]
-            (requireActivity() as SlotSelectedListener).onSlotSelected(channel.slotId, channel.portId)
+            val slotId = slotIds[spinner.selectedItemPosition]
+            val portId = portIds[spinner.selectedItemPosition]
+            (requireActivity() as SlotSelectedListener).onSlotSelected(slotId, portId)
             dismiss()
             true
         }
-
-        return view
     }
 
     override fun onResume() {

+ 2 - 2
app/src/main/java/im/angry/openeuicc/util/PrivilegedTelephonyUtils.kt

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