Browse Source

feat: multi-SE support for USB channels (#309)

All channels now support multi-SE. UsbCcidReaderFragment now no longer wraps inner EuiccManagementFragment's but instead calls MainActivity to instantiate tabs for USB channels. Also, we now no longer use the USB product name as channel titles but instead use "USB Reader" and "USB Reader, SE x" for all of them.

Reviewed-on: https://gitea.angry.im/PeterCxy/OpenEUICC/pulls/309
Reviewed-by: septs <github@septs.pw>
Peter Cai 4 weeks ago
parent
commit
713ffec26d

+ 34 - 0
.idea/codeStyles/Project.xml

@@ -1,5 +1,39 @@
 <component name="ProjectCodeStyleConfiguration">
   <code_scheme name="Project" version="173">
+    <JavaCodeStyleSettings>
+      <option name="IMPORT_LAYOUT_TABLE">
+        <value>
+          <package name="android" withSubpackages="true" static="true" />
+          <package name="androidx" withSubpackages="true" static="true" />
+          <package name="com" withSubpackages="true" static="true" />
+          <package name="junit" withSubpackages="true" static="true" />
+          <package name="net" withSubpackages="true" static="true" />
+          <package name="org" withSubpackages="true" static="true" />
+          <package name="java" withSubpackages="true" static="true" />
+          <package name="javax" withSubpackages="true" static="true" />
+          <package name="" withSubpackages="true" static="true" />
+          <emptyLine />
+          <package name="android" withSubpackages="true" static="false" />
+          <emptyLine />
+          <package name="androidx" withSubpackages="true" static="false" />
+          <emptyLine />
+          <package name="com" withSubpackages="true" static="false" />
+          <emptyLine />
+          <package name="junit" withSubpackages="true" static="false" />
+          <emptyLine />
+          <package name="net" withSubpackages="true" static="false" />
+          <emptyLine />
+          <package name="org" withSubpackages="true" static="false" />
+          <emptyLine />
+          <package name="java" withSubpackages="true" static="false" />
+          <emptyLine />
+          <package name="javax" withSubpackages="true" static="false" />
+          <emptyLine />
+          <package name="" withSubpackages="true" static="false" />
+          <emptyLine />
+        </value>
+      </option>
+    </JavaCodeStyleSettings>
     <JetCodeStyleSettings>
       <option name="PACKAGES_TO_USE_STAR_IMPORTS">
         <value>

+ 0 - 2
app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelFactory.kt

@@ -38,7 +38,6 @@ open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccCha
         EuiccChannelImpl(
             context.getString(R.string.channel_type_omapi),
             port,
-            intrinsicChannelName = null,
             OmapiApduInterface(
                 seService!!,
                 port,
@@ -67,7 +66,6 @@ open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccCha
         EuiccChannelImpl(
             context.getString(R.string.channel_type_usb),
             FakeUiccPortInfoCompat(FakeUiccCardInfoCompat(EuiccChannelManager.USB_CHANNEL_ID)),
-            intrinsicChannelName = ccidCtx.productName,
             UsbApduInterface(
                 ccidCtx
             ),

+ 6 - 9
app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelManager.kt

@@ -52,7 +52,6 @@ open class DefaultEuiccChannelManager(
         get() = (0..<tm.activeModemCountCompat).map { FakeUiccCardInfoCompat(it) }
 
     private suspend inline fun tryOpenChannelWithKnownAids(
-        supportsMultiSE: Boolean,
         openFn: (ByteArray, EuiccChannel.SecureElementId) -> EuiccChannel?
     ): List<EuiccChannel> {
         var isdrAidList =
@@ -100,10 +99,9 @@ open class DefaultEuiccChannelManager(
                     ret.add(channel)
                     openedAids.add(aid)
 
-                    // Don't try opening more than 1 channel unless we support multi SE or
-                    // there is a vendor implementation for deciding when we should stop
-                    // opening more channels
-                    if (!supportsMultiSE || vendorDecider == null) {
+                    // Don't try opening more than 1 channel unless there is a vendor
+                    // implementation for deciding when we should stop opening more channels
+                    if (vendorDecider == null) {
                         break@outer
                     }
                 }
@@ -149,9 +147,9 @@ open class DefaultEuiccChannelManager(
                 return null
             }
 
-            // This function is not responsible for managing USB channels (see the initial check), so supportsMultiSE is true.
+            // This function is not responsible for managing USB channels (see the initial check)
             val channels =
-                tryOpenChannelWithKnownAids(supportsMultiSE = true) { isdrAid, seId ->
+                tryOpenChannelWithKnownAids { isdrAid, seId ->
                     euiccChannelFactory.tryOpenEuiccChannel(
                         port,
                         isdrAid,
@@ -379,8 +377,7 @@ open class DefaultEuiccChannelManager(
                     UsbCcidContext.createFromUsbDevice(context, device, iface) ?: return@forEach
 
                 try {
-                    // TODO: We should also support multiple SEs over USB readers (the code here already does, UI doesn't yet)
-                    val channels = tryOpenChannelWithKnownAids(supportsMultiSE = false) { isdrAid, seId ->
+                    val channels = tryOpenChannelWithKnownAids { isdrAid, seId ->
                         euiccChannelFactory.tryOpenUsbEuiccChannel(ccidCtx, isdrAid, seId)
                     }
                     if (channels.isNotEmpty() && channels[0].valid) {

+ 0 - 7
app-common/src/main/java/im/angry/openeuicc/core/EuiccChannel.kt

@@ -85,13 +85,6 @@ interface EuiccChannel {
      */
     val atr: ByteArray?
 
-    /**
-     * Intrinsic name of this channel. For device-internal SIM slots,
-     * this should be null; for USB readers, this should be the name of
-     * the reader device.
-     */
-    val intrinsicChannelName: String?
-
     /**
      * The underlying APDU interface for this channel
      */

+ 0 - 1
app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelImpl.kt

@@ -12,7 +12,6 @@ import net.typeblog.lpac_jni.impl.LocalProfileAssistantImpl
 class EuiccChannelImpl(
     override val type: String,
     override val port: UiccPortInfoCompat,
-    override val intrinsicChannelName: String?,
     override val apduInterface: ApduInterface,
     override val isdrAid: ByteArray,
     override val seId: EuiccChannel.SecureElementId,

+ 0 - 2
app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelWrapper.kt

@@ -34,8 +34,6 @@ class EuiccChannelWrapper(orig: EuiccChannel) : EuiccChannel {
     override val lpa: LocalProfileAssistant by lpaDelegate
     override val valid: Boolean
         get() = channel.valid
-    override val intrinsicChannelName: String?
-        get() = channel.intrinsicChannelName
     override val apduInterface: ApduInterface
         get() = channel.apduInterface
     override val atr: ByteArray?

+ 0 - 2
app-common/src/main/java/im/angry/openeuicc/core/usb/UsbCcidContext.kt

@@ -20,7 +20,6 @@ class UsbCcidContext private constructor(
     private val conn: UsbDeviceConnection,
     private val bulkIn: UsbEndpoint,
     private val bulkOut: UsbEndpoint,
-    val productName: String,
     val verboseLoggingFlow: Flow<Boolean>
 ) {
     companion object {
@@ -38,7 +37,6 @@ class UsbCcidContext private constructor(
                 conn,
                 bulkIn,
                 bulkOut,
-                usbDevice.productName ?: "USB",
                 context.preferenceRepository.verboseLoggingFlow
             )
         }.getOrNull()

+ 7 - 7
app-common/src/main/java/im/angry/openeuicc/ui/EuiccInfoActivity.kt

@@ -78,7 +78,7 @@ class EuiccInfoActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
 
         setChannelTitle(
             if (logicalSlotId == EuiccChannelManager.USB_CHANNEL_ID)
-                getString(R.string.channel_type_usb) else
+                getString(R.string.channel_name_format_usb) else
                 appContainer.customizableTextProvider.formatNonUsbChannelName(logicalSlotId)
         )
 
@@ -114,13 +114,13 @@ class EuiccInfoActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
 
         lifecycleScope.launch {
             euiccChannelManager.withEuiccChannel(logicalSlotId, seId) { channel ->
-                // When the chip multi-SE, we need to include seId in the title (because we don't have access
-                // to hasMultipleSE in the onCreate() function, we need to do it here).
-                // TODO: Move channel formatting to somewhere centralized and remove this hack. (And also, of course, add support for USB)
-                if (channel.hasMultipleSE && logicalSlotId != EuiccChannelManager.USB_CHANNEL_ID) {
+                if (channel.hasMultipleSE) {
                     withContext(Dispatchers.Main) {
-                        val title = appContainer.customizableTextProvider
-                            .formatNonUsbChannelNameWithSeId(logicalSlotId, seId)
+                        val title = if (logicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) {
+                            getString(R.string.channel_name_format_usb_se, seId.id)
+                        } else {
+                            appContainer.customizableTextProvider.formatNonUsbChannelNameWithSeId(logicalSlotId, seId)
+                        }
                         setChannelTitle(title)
                     }
                 }

+ 48 - 5
app-common/src/main/java/im/angry/openeuicc/ui/MainActivity.kt

@@ -26,6 +26,7 @@ import androidx.viewpager2.widget.ViewPager2
 import com.google.android.material.tabs.TabLayout
 import com.google.android.material.tabs.TabLayoutMediator
 import im.angry.openeuicc.common.R
+import im.angry.openeuicc.core.EuiccChannel
 import im.angry.openeuicc.core.EuiccChannelManager
 import im.angry.openeuicc.ui.wizard.DownloadWizardActivity
 import im.angry.openeuicc.util.*
@@ -51,18 +52,30 @@ open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
     private var refreshing = false
 
     private data class Page(
+        val id: Long,
         val logicalSlotId: Int,
         val title: String,
         val createFragment: () -> Fragment
     )
 
     private val pages: MutableList<Page> = mutableListOf()
+    private var nextPageId = 0L
+
+    private fun newPage(
+        logicalSlotId: Int,
+        title: String,
+        createFragment: () -> Fragment
+    ): Page = Page(nextPageId++, logicalSlotId, title, createFragment)
 
     private val pagerAdapter by lazy {
         object : FragmentStateAdapter(this) {
             override fun getItemCount() = pages.size
 
             override fun createFragment(position: Int): Fragment = pages[position].createFragment()
+
+            override fun getItemId(position: Int): Long = pages[position].id
+
+            override fun containsItem(itemId: Long): Boolean = pages.any { it.id == itemId }
         }
     }
 
@@ -178,7 +191,7 @@ open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
                     } else {
                         appContainer.customizableTextProvider.formatNonUsbChannelName(channel.logicalSlotId)
                     }
-                    newPages.add(Page(channel.logicalSlotId, channelName) {
+                    newPages.add(newPage(channel.logicalSlotId, channelName) {
                         appContainer.uiComponentFactory.createEuiccManagementFragment(
                             slotId,
                             portId,
@@ -192,9 +205,8 @@ open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
         // If USB readers exist, add them at the very last
         // We use a wrapper fragment to handle logic specific to USB readers
         usbDevice?.let {
-            val productName = it.productName ?: getString(R.string.channel_type_usb)
-            newPages.add(Page(EuiccChannelManager.USB_CHANNEL_ID, productName) {
-                UsbCcidReaderFragment()
+            newPages.add(newPage(EuiccChannelManager.USB_CHANNEL_ID, getString(R.string.channel_name_format_usb)) {
+                UsbCcidReaderPermissionFragment()
             })
         }
         viewPager.visibility = View.VISIBLE
@@ -202,7 +214,7 @@ open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
         if (newPages.size > 1) {
             tabs.visibility = View.VISIBLE
         } else if (newPages.isEmpty()) {
-            newPages.add(Page(-1, "") {
+            newPages.add(newPage(-1, "") {
                 appContainer.uiComponentFactory.createNoEuiccPlaceholderFragment()
             })
         }
@@ -260,4 +272,35 @@ open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
             .build()
         return listOf(downloadShortcut)
     }
+
+    fun instantiateUsbTabs(seIds: List<EuiccChannel.SecureElementId>) {
+        val existingUsbPageIndex = pages.indexOfFirst { it.logicalSlotId == EuiccChannelManager.USB_CHANNEL_ID }
+        if (existingUsbPageIndex == -1) return
+
+        val usbPages =
+            seIds.map { seId ->
+                val name = if (seIds.size == 1) {
+                    getString(R.string.channel_name_format_usb)
+                } else {
+                    getString(R.string.channel_name_format_usb_se, seId.id)
+                }
+                newPage(EuiccChannelManager.USB_CHANNEL_ID, name) {
+                    appContainer.uiComponentFactory.createEuiccManagementFragment(
+                        EuiccChannelManager.USB_CHANNEL_ID,
+                        0,
+                        seId
+                    )
+                }
+            }
+
+        // Add before removing to avoid out-of-bounds problems
+        pages.addAll(existingUsbPageIndex, usbPages)
+        // Remove the old USB reader page
+        pages.removeAt(existingUsbPageIndex + usbPages.size)
+
+        if (pages.size > 1) {
+            tabs.visibility = View.VISIBLE
+        }
+        pagerAdapter.notifyDataSetChanged()
+    }
 }

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

@@ -72,11 +72,9 @@ class NotificationsActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker
             intent.getParcelableExtra("seId")
         } ?: EuiccChannel.SecureElementId.DEFAULT
 
-        // This is slightly different from the MainActivity logic
-        // due to the length (we don't want to display the full USB product name)
         setChannelTitle(
             if (logicalSlotId == EuiccChannelManager.USB_CHANNEL_ID)
-                getString(R.string.channel_type_usb) else
+                getString(R.string.channel_name_format_usb) else
                 appContainer.customizableTextProvider.formatNonUsbChannelName(logicalSlotId)
         )
 
@@ -135,10 +133,13 @@ class NotificationsActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker
     private fun refresh() {
         launchTask {
             notificationAdapter.notifications = withEuiccChannel { channel ->
-                if (channel.hasMultipleSE && logicalSlotId != EuiccChannelManager.USB_CHANNEL_ID) {
+                if (channel.hasMultipleSE) {
                     withContext(Dispatchers.Main) {
-                        val channelTitle = appContainer.customizableTextProvider
-                            .formatNonUsbChannelNameWithSeId(logicalSlotId, seId)
+                        val channelTitle = if (logicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) {
+                            getString(R.string.channel_name_format_usb_se, seId.id)
+                        } else {
+                            appContainer.customizableTextProvider.formatNonUsbChannelNameWithSeId(logicalSlotId, seId)
+                        }
                         setChannelTitle(channelTitle)
                     }
                 }

+ 12 - 21
app-common/src/main/java/im/angry/openeuicc/ui/UsbCcidReaderFragment.kt → app-common/src/main/java/im/angry/openeuicc/ui/UsbCcidReaderPermissionFragment.kt

@@ -14,24 +14,22 @@ import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
 import android.widget.Button
-import android.widget.ProgressBar
 import android.widget.TextView
 import androidx.fragment.app.Fragment
-import androidx.fragment.app.commit
 import androidx.lifecycle.lifecycleScope
 import im.angry.openeuicc.common.R
-import im.angry.openeuicc.core.EuiccChannel
 import im.angry.openeuicc.core.EuiccChannelManager
 import im.angry.openeuicc.util.*
 import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.toList
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 
 /**
- * A wrapper fragment over EuiccManagementFragment where we handle
- * logic specific to USB devices. This is mainly USB permission
- * requests, and the fact that USB devices may or may not be
- * available by the time the user selects it from MainActivity.
+ * A fragment to handle USB reader-specific permission flow. If/after
+ * permission is granted, this fragment simply calls back to MainActivity
+ * to instantiate the corresponding EuiccManagementFragment(s) for the USB
+ * reader.
  *
  * Having this fragment allows MainActivity to be (mostly) agnostic
  * of the underlying implementation of different types of channels.
@@ -41,7 +39,7 @@ import kotlinx.coroutines.withContext
  * Note that for now we assume there will only be one USB card reader
  * device. This is also an implicit assumption in EuiccChannelManager.
  */
-class UsbCcidReaderFragment : Fragment(), OpenEuiccContextMarker {
+class UsbCcidReaderPermissionFragment : Fragment(), OpenEuiccContextMarker {
     companion object {
         const val ACTION_USB_PERMISSION = "im.angry.openeuicc.USB_PERMISSION"
     }
@@ -70,7 +68,7 @@ class UsbCcidReaderFragment : Fragment(), OpenEuiccContextMarker {
 
     private lateinit var text: TextView
     private lateinit var permissionButton: Button
-    private lateinit var loadingProgress: ProgressBar
+    private lateinit var loadingProgress: View
 
     private var usbDevice: UsbDevice? = null
 
@@ -143,27 +141,20 @@ class UsbCcidReaderFragment : Fragment(), OpenEuiccContextMarker {
             euiccChannelManager.tryOpenUsbEuiccChannel()
         }
 
-        loadingProgress.visibility = View.GONE
-
         usbDevice = device
 
         if (device != null && !canOpen && !usbManager.hasPermission(device)) {
+            loadingProgress.visibility = View.GONE
             text.text = getString(R.string.usb_permission_needed)
             text.visibility = View.VISIBLE
             permissionButton.visibility = View.VISIBLE
         } else if (device != null && canOpen) {
-            childFragmentManager.commit {
-                replace(
-                    R.id.child_container,
-                    appContainer.uiComponentFactory.createEuiccManagementFragment(
-                        slotId = EuiccChannelManager.USB_CHANNEL_ID,
-                        portId = 0,
-                        // TODO: What if a USB card has multiple SEs?
-                        seId = EuiccChannel.SecureElementId.DEFAULT
-                    )
-                )
+            val seIds = withContext(Dispatchers.IO) {
+                euiccChannelManager.flowEuiccSecureElements(EuiccChannelManager.USB_CHANNEL_ID, 0).toList()
             }
+            (requireActivity() as MainActivity).instantiateUsbTabs(seIds)
         } else {
+            loadingProgress.visibility = View.GONE
             text.text = getString(R.string.usb_failed)
             text.visibility = View.VISIBLE
             permissionButton.visibility = View.GONE

+ 5 - 3
app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt

@@ -41,7 +41,6 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt
         val freeSpace: Int,
         val imei: String,
         val enabledProfileName: String?,
-        val intrinsicChannelName: String?,
     ) {
         // A synthetic slot ID used to uniquely identify this slot + SE chip in the download process
         // We assume we don't have anywhere near 2^16 ports...
@@ -115,7 +114,6 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt
                             ""
                         },
                         channel.lpa.profiles.enabled?.displayName,
-                        channel.intrinsicChannelName,
                     )
                 }
             }
@@ -188,7 +186,11 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt
             }
 
             title.text = if (item.logicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) {
-                item.intrinsicChannelName ?: root.context.getString(R.string.channel_type_usb)
+                if (item.hasMultipleSEs) {
+                    root.context.getString(R.string.channel_name_format_usb_se, item.seId.id)
+                } else {
+                    root.context.getString(R.string.channel_name_format_usb)
+                }
             } else if (item.hasMultipleSEs) {
                 appContainer.customizableTextProvider.formatNonUsbChannelNameWithSeId(
                     item.logicalSlotId,

+ 13 - 14
app-common/src/main/res/layout/fragment_usb_ccid_reader.xml

@@ -4,15 +4,21 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
-    <ProgressBar
+    <androidx.constraintlayout.widget.ConstraintLayout
         android:id="@+id/loading"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:indeterminate="true"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent" />
+        app:layout_constraintTop_toTopOf="parent">
+        <ProgressBar
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:indeterminate="true"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+    </androidx.constraintlayout.widget.ConstraintLayout>
 
     <TextView
         android:id="@+id/usb_reader_text"
@@ -37,11 +43,4 @@
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toBottomOf="@id/usb_reader_text" />
 
-    <FrameLayout
-        android:id="@+id/child_container"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent" />
-
 </androidx.constraintlayout.widget.ConstraintLayout>

+ 2 - 0
app-common/src/main/res/values-ja/strings.xml

@@ -195,4 +195,6 @@
     <string name="pref_info_website">公式サイト</string>
     <string name="pref_info_source_code">ソースコード</string>
     <string name="channel_name_format_se">論理スロット %1$d, SE %2$d</string>
+    <string name="channel_name_format_usb">USB リーダー</string>
+    <string name="channel_name_format_usb_se">USB リーダー, SE %d</string>
 </resources>

+ 2 - 0
app-common/src/main/res/values-zh-rCN/strings.xml

@@ -193,4 +193,6 @@
     <string name="download_wizard_error_suggest_contact_carrier">请联系您的运营商寻求帮助。</string>
     <string name="download_wizard_error_suggest_contact_reissue">请联系您的运营商重新签发此 eSIM 配置文件。</string>
     <string name="channel_name_format_se">逻辑卡槽 %1$d, SE %2$d</string>
+    <string name="channel_name_format_usb">USB 读卡器</string>
+    <string name="channel_name_format_usb_se">USB 读卡器, SE %d</string>
 </resources>

+ 2 - 0
app-common/src/main/res/values-zh-rTW/strings.xml

@@ -189,4 +189,6 @@
     <string name="download_wizard_error_suggest_contact_carrier">請聯絡您的電信業者尋求協助。</string>
     <string name="download_wizard_error_suggest_contact_reissue">請聯絡您的電信業者重新簽發此 eSIM 設定檔。</string>
     <string name="channel_name_format_se">虛擬卡槽 %1$d, SE %2$d</string>
+    <string name="channel_name_format_usb">USB 讀卡機</string>
+    <string name="channel_name_format_usb_se">USB 讀卡機, SE %d</string>
 </resources>

+ 2 - 0
app-common/src/main/res/values/strings.xml

@@ -11,6 +11,8 @@
     <string name="channel_name_format">Logical Slot %d</string>
     <string name="channel_name_format_se">Logical Slot %1$d, SE %2$d</string>
     <string name="channel_type_usb" translatable="false">USB</string>
+    <string name="channel_name_format_usb">USB Reader</string>
+    <string name="channel_name_format_usb_se">USB Reader, SE %d</string>
     <string name="channel_type_omapi" translatable="false">OpenMobile API (OMAPI)</string>
 
     <!-- Profile -->

+ 0 - 1
app/src/main/java/im/angry/openeuicc/core/PrivilegedEuiccChannelFactory.kt

@@ -33,7 +33,6 @@ class PrivilegedEuiccChannelFactory(context: Context) : DefaultEuiccChannelFacto
                 return EuiccChannelImpl(
                     context.getString(R.string.channel_type_telephony_manager),
                     port,
-                    intrinsicChannelName = null,
                     TelephonyManagerApduInterface(
                         port,
                         telephonyManager,