Compare commits
No commits in common. "0a971b68b21e29793841a3fb5c777415b9c5f1a8" and "70f20f9de83a29eb5d2d305920aad1c6e43e528c" have entirely different histories.
0a971b68b2
...
70f20f9de8
6 changed files with 81 additions and 244 deletions
|
@ -1,7 +1,14 @@
|
||||||
package im.angry.openeuicc.ui
|
package im.angry.openeuicc.ui
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.hardware.usb.UsbDevice
|
||||||
|
import android.hardware.usb.UsbManager
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.telephony.TelephonyManager
|
import android.telephony.TelephonyManager
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
@ -10,11 +17,10 @@ import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.AdapterView
|
import android.widget.AdapterView
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import android.widget.ProgressBar
|
|
||||||
import android.widget.Spinner
|
import android.widget.Spinner
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import im.angry.openeuicc.common.R
|
import im.angry.openeuicc.common.R
|
||||||
|
import im.angry.openeuicc.core.EuiccChannel
|
||||||
import im.angry.openeuicc.util.*
|
import im.angry.openeuicc.util.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -23,37 +29,61 @@ import kotlinx.coroutines.withContext
|
||||||
open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "MainActivity"
|
const val TAG = "MainActivity"
|
||||||
|
const val ACTION_USB_PERMISSION = "im.angry.openeuicc.USB_PERMISSION"
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var spinnerAdapter: ArrayAdapter<String>
|
private lateinit var spinnerAdapter: ArrayAdapter<String>
|
||||||
private lateinit var spinnerItem: MenuItem
|
private lateinit var spinnerItem: MenuItem
|
||||||
private lateinit var spinner: Spinner
|
private lateinit var spinner: Spinner
|
||||||
private lateinit var loadingProgress: ProgressBar
|
|
||||||
|
|
||||||
var loading: Boolean
|
private val fragments = arrayListOf<EuiccManagementFragment>()
|
||||||
get() = loadingProgress.visibility == View.VISIBLE
|
|
||||||
set(value) {
|
|
||||||
loadingProgress.visibility = if (value) {
|
|
||||||
View.VISIBLE
|
|
||||||
} else {
|
|
||||||
View.GONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val fragments = arrayListOf<Fragment>()
|
|
||||||
|
|
||||||
protected lateinit var tm: TelephonyManager
|
protected lateinit var tm: TelephonyManager
|
||||||
|
|
||||||
|
private val usbManager: UsbManager by lazy {
|
||||||
|
getSystemService(USB_SERVICE) as UsbManager
|
||||||
|
}
|
||||||
|
|
||||||
|
private var usbDevice: UsbDevice? = null
|
||||||
|
private var usbChannel: EuiccChannel? = null
|
||||||
|
|
||||||
|
private lateinit var usbPendingIntent: PendingIntent
|
||||||
|
|
||||||
|
private val usbPermissionReceiver = object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
|
if (intent?.action == ACTION_USB_PERMISSION) {
|
||||||
|
if (usbDevice != null && usbManager.hasPermission(usbDevice)) {
|
||||||
|
lifecycleScope.launch(Dispatchers.Main) {
|
||||||
|
switchToUsbFragmentIfPossible()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("WrongConstant", "UnspecifiedRegisterReceiverFlag")
|
@SuppressLint("WrongConstant", "UnspecifiedRegisterReceiverFlag")
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_main)
|
setContentView(R.layout.activity_main)
|
||||||
setSupportActionBar(requireViewById(R.id.toolbar))
|
setSupportActionBar(requireViewById(R.id.toolbar))
|
||||||
loadingProgress = requireViewById(R.id.loading)
|
|
||||||
|
supportFragmentManager.beginTransaction().replace(
|
||||||
|
R.id.fragment_root,
|
||||||
|
appContainer.uiComponentFactory.createNoEuiccPlaceholderFragment()
|
||||||
|
).commit()
|
||||||
|
|
||||||
tm = telephonyManager
|
tm = telephonyManager
|
||||||
|
|
||||||
spinnerAdapter = ArrayAdapter<String>(this, R.layout.spinner_item)
|
spinnerAdapter = ArrayAdapter<String>(this, R.layout.spinner_item)
|
||||||
|
|
||||||
|
usbPendingIntent = PendingIntent.getBroadcast(this, 0,
|
||||||
|
Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_IMMUTABLE)
|
||||||
|
val filter = IntentFilter(ACTION_USB_PERMISSION)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
registerReceiver(usbPermissionReceiver, filter, Context.RECEIVER_EXPORTED)
|
||||||
|
} else {
|
||||||
|
registerReceiver(usbPermissionReceiver, filter)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
|
@ -76,6 +106,11 @@ open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
||||||
if (position < fragments.size) {
|
if (position < fragments.size) {
|
||||||
supportFragmentManager.beginTransaction()
|
supportFragmentManager.beginTransaction()
|
||||||
.replace(R.id.fragment_root, fragments[position]).commit()
|
.replace(R.id.fragment_root, fragments[position]).commit()
|
||||||
|
} else if (position == fragments.size) {
|
||||||
|
// If we are at the last position, this is the USB device
|
||||||
|
lifecycleScope.launch(Dispatchers.Main) {
|
||||||
|
switchToUsbFragmentIfPossible()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,8 +143,6 @@ open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun init() {
|
private suspend fun init() {
|
||||||
loading = true
|
|
||||||
|
|
||||||
val knownChannels = withContext(Dispatchers.IO) {
|
val knownChannels = withContext(Dispatchers.IO) {
|
||||||
euiccChannelManager.enumerateEuiccChannels().onEach {
|
euiccChannelManager.enumerateEuiccChannels().onEach {
|
||||||
Log.d(TAG, "slot ${it.slotId} port ${it.portId}")
|
Log.d(TAG, "slot ${it.slotId} port ${it.portId}")
|
||||||
|
@ -121,37 +154,51 @@ open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val (usbDevice, _) = withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
euiccChannelManager.enumerateUsbEuiccChannel()
|
val res = euiccChannelManager.enumerateUsbEuiccChannel()
|
||||||
|
usbDevice = res.first
|
||||||
|
usbChannel = res.second
|
||||||
}
|
}
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
loading = false
|
|
||||||
|
|
||||||
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))
|
||||||
}
|
}
|
||||||
|
|
||||||
// If USB readers exist, add them at the very last
|
// If USB readers exist, add them at the very last
|
||||||
// We use a wrapper fragment to handle logic specific to USB readers
|
// The adapter logic depends on this assumption
|
||||||
usbDevice?.let {
|
usbDevice?.let { spinnerAdapter.add(it.productName) }
|
||||||
spinnerAdapter.add(it.productName)
|
|
||||||
fragments.add(UsbCcidReaderFragment())
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fragments.isNotEmpty()) {
|
if (fragments.isNotEmpty()) {
|
||||||
if (this@MainActivity::spinner.isInitialized) {
|
if (this@MainActivity::spinner.isInitialized) {
|
||||||
spinnerItem.isVisible = true
|
spinnerItem.isVisible = true
|
||||||
}
|
}
|
||||||
supportFragmentManager.beginTransaction()
|
supportFragmentManager.beginTransaction().replace(R.id.fragment_root, fragments.first()).commit()
|
||||||
.replace(R.id.fragment_root, fragments.first()).commit()
|
|
||||||
} else {
|
|
||||||
supportFragmentManager.beginTransaction().replace(
|
|
||||||
R.id.fragment_root,
|
|
||||||
appContainer.uiComponentFactory.createNoEuiccPlaceholderFragment()
|
|
||||||
).commit()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun switchToUsbFragmentIfPossible() {
|
||||||
|
if (usbDevice != null && usbChannel == null) {
|
||||||
|
if (!usbManager.hasPermission(usbDevice)) {
|
||||||
|
usbManager.requestPermission(usbDevice, usbPendingIntent)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
val (device, channel) = withContext(Dispatchers.IO) {
|
||||||
|
euiccChannelManager.enumerateUsbEuiccChannel()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (device != null && channel != null) {
|
||||||
|
usbDevice = device
|
||||||
|
usbChannel = channel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (usbChannel != null) {
|
||||||
|
supportFragmentManager.beginTransaction().replace(R.id.fragment_root,
|
||||||
|
appContainer.uiComponentFactory.createEuiccManagementFragment(usbChannel!!)).commit()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -151,7 +151,7 @@ class ProfileDownloadFragment : BaseMaterialDialogFragment(),
|
||||||
super.onStart()
|
super.onStart()
|
||||||
profileDownloadIMEI.editText!!.text = Editable.Factory.getInstance().newEditable(
|
profileDownloadIMEI.editText!!.text = Editable.Factory.getInstance().newEditable(
|
||||||
try {
|
try {
|
||||||
telephonyManager.getImei(channel.logicalSlotId) ?: ""
|
telephonyManager.getImei(channel.logicalSlotId)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,159 +0,0 @@
|
||||||
package im.angry.openeuicc.ui
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.app.PendingIntent
|
|
||||||
import android.content.BroadcastReceiver
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.IntentFilter
|
|
||||||
import android.hardware.usb.UsbDevice
|
|
||||||
import android.hardware.usb.UsbManager
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.Button
|
|
||||||
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.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.
|
|
||||||
*
|
|
||||||
* Having this fragment allows MainActivity to be (mostly) agnostic
|
|
||||||
* of the underlying implementation of different types of channels.
|
|
||||||
* When permission is granted, this fragment will simply load
|
|
||||||
* EuiccManagementFragment using its own childFragmentManager.
|
|
||||||
*
|
|
||||||
* 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 {
|
|
||||||
companion object {
|
|
||||||
const val ACTION_USB_PERMISSION = "im.angry.openeuicc.USB_PERMISSION"
|
|
||||||
}
|
|
||||||
|
|
||||||
private val euiccChannelManager: EuiccChannelManager by lazy {
|
|
||||||
(requireActivity() as MainActivity).euiccChannelManager
|
|
||||||
}
|
|
||||||
|
|
||||||
private val usbManager: UsbManager by lazy {
|
|
||||||
requireContext().getSystemService(Context.USB_SERVICE) as UsbManager
|
|
||||||
}
|
|
||||||
|
|
||||||
private val usbPermissionReceiver = object : BroadcastReceiver() {
|
|
||||||
override fun onReceive(context: Context?, intent: Intent?) {
|
|
||||||
if (intent?.action == ACTION_USB_PERMISSION) {
|
|
||||||
if (usbDevice != null && usbManager.hasPermission(usbDevice)) {
|
|
||||||
lifecycleScope.launch(Dispatchers.Main) {
|
|
||||||
tryLoadUsbChannel()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private lateinit var usbPendingIntent: PendingIntent
|
|
||||||
|
|
||||||
private lateinit var text: TextView
|
|
||||||
private lateinit var permissionButton: Button
|
|
||||||
|
|
||||||
private var usbDevice: UsbDevice? = null
|
|
||||||
private var usbChannel: EuiccChannel? = null
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater,
|
|
||||||
container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?
|
|
||||||
): View? {
|
|
||||||
val view = inflater.inflate(R.layout.fragment_usb_ccid_reader, container, false)
|
|
||||||
|
|
||||||
text = view.requireViewById(R.id.usb_reader_text)
|
|
||||||
permissionButton = view.requireViewById(R.id.usb_grant_permission)
|
|
||||||
|
|
||||||
permissionButton.setOnClickListener {
|
|
||||||
usbManager.requestPermission(usbDevice, usbPendingIntent)
|
|
||||||
}
|
|
||||||
|
|
||||||
return view
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("UnspecifiedRegisterReceiverFlag", "WrongConstant")
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
|
|
||||||
usbPendingIntent = PendingIntent.getBroadcast(
|
|
||||||
requireContext(), 0,
|
|
||||||
Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_IMMUTABLE
|
|
||||||
)
|
|
||||||
val filter = IntentFilter(ACTION_USB_PERMISSION)
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
||||||
requireContext().registerReceiver(
|
|
||||||
usbPermissionReceiver,
|
|
||||||
filter,
|
|
||||||
Context.RECEIVER_EXPORTED
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
requireContext().registerReceiver(usbPermissionReceiver, filter)
|
|
||||||
}
|
|
||||||
|
|
||||||
lifecycleScope.launch(Dispatchers.Main) {
|
|
||||||
tryLoadUsbChannel()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDetach() {
|
|
||||||
super.onDetach()
|
|
||||||
requireContext().unregisterReceiver(usbPermissionReceiver)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroy() {
|
|
||||||
super.onDestroy()
|
|
||||||
requireContext().unregisterReceiver(usbPermissionReceiver)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun tryLoadUsbChannel() {
|
|
||||||
text.visibility = View.GONE
|
|
||||||
permissionButton.visibility = View.GONE
|
|
||||||
|
|
||||||
(requireActivity() as MainActivity).loading = true
|
|
||||||
|
|
||||||
val (device, channel) = withContext(Dispatchers.IO) {
|
|
||||||
euiccChannelManager.enumerateUsbEuiccChannel()
|
|
||||||
}
|
|
||||||
|
|
||||||
(requireActivity() as MainActivity).loading = false
|
|
||||||
|
|
||||||
usbDevice = device
|
|
||||||
usbChannel = channel
|
|
||||||
|
|
||||||
if (device != null && channel == null && !usbManager.hasPermission(device)) {
|
|
||||||
text.text = getString(R.string.usb_permission_needed)
|
|
||||||
text.visibility = View.VISIBLE
|
|
||||||
permissionButton.visibility = View.VISIBLE
|
|
||||||
} else if (device != null && channel != null) {
|
|
||||||
childFragmentManager.commit {
|
|
||||||
replace(
|
|
||||||
R.id.child_container,
|
|
||||||
appContainer.uiComponentFactory.createEuiccManagementFragment(channel)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
text.text = getString(R.string.usb_failed)
|
|
||||||
text.visibility = View.VISIBLE
|
|
||||||
permissionButton.visibility = View.GONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -14,16 +14,6 @@
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
app:layout_constraintWidth_percent="1" />
|
app:layout_constraintWidth_percent="1" />
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/loading"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:indeterminate="true"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/toolbar"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent" />
|
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/fragment_root"
|
android:id="@+id/fragment_root"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
|
|
|
@ -1,37 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/usb_reader_text"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginHorizontal="40dp"
|
|
||||||
android:gravity="center"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/usb_grant_permission"
|
|
||||||
android:text="@string/usb_permission"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="20dp"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/usb_reader_text"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent" />
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:id="@+id/child_container"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
|
@ -25,10 +25,6 @@
|
||||||
<string name="slot_select">Select Slot</string>
|
<string name="slot_select">Select Slot</string>
|
||||||
<string name="slot_select_select">Select</string>
|
<string name="slot_select_select">Select</string>
|
||||||
|
|
||||||
<string name="usb_permission">Grant USB permission</string>
|
|
||||||
<string name="usb_permission_needed">Permission is needed to access the USB smart card reader.</string>
|
|
||||||
<string name="usb_failed">Cannot connect to eSIM via a USB smart card reader.</string>
|
|
||||||
|
|
||||||
<string name="profile_download">New eSIM</string>
|
<string name="profile_download">New eSIM</string>
|
||||||
<string name="profile_download_server">Server (RSP / SM-DP+)</string>
|
<string name="profile_download_server">Server (RSP / SM-DP+)</string>
|
||||||
<string name="profile_download_code">Activation Code</string>
|
<string name="profile_download_code">Activation Code</string>
|
||||||
|
|
Loading…
Add table
Reference in a new issue