Compare commits

..

No commits in common. "d0b3d54c6662be0f6c7e8668ff7389f825d26956" and "43f247a71bec450fa75856cfdc3ee4d38968e354" have entirely different histories.

19 changed files with 38 additions and 107 deletions

View file

@ -1,5 +0,0 @@
package im.angry.openeuicc.core
interface ApduInterfaceAtrProvider {
val atr: ByteArray?
}

View file

@ -184,30 +184,20 @@ open class DefaultEuiccChannelManager(
} }
override suspend fun waitForReconnect(physicalSlotId: Int, portId: Int, timeoutMillis: Long) { override suspend fun waitForReconnect(physicalSlotId: Int, portId: Int, timeoutMillis: Long) {
if (physicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) { if (physicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) return
usbChannel?.close()
usbChannel = null // If there is already a valid channel, we close it proactively
} else { // Sometimes the current channel can linger on for a bit even after it should have become invalid
// If there is already a valid channel, we close it proactively channelCache.find { it.slotId == physicalSlotId && it.portId == portId }?.apply {
// Sometimes the current channel can linger on for a bit even after it should have become invalid if (valid) close()
channelCache.find { it.slotId == physicalSlotId && it.portId == portId }?.apply {
if (valid) close()
}
} }
withTimeout(timeoutMillis) { withTimeout(timeoutMillis) {
while (true) { while (true) {
try { try {
val channel = if (physicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) { // tryOpenEuiccChannel() will automatically dispose of invalid channels
// tryOpenUsbEuiccChannel() will always try to reopen the channel, even if // and recreate when needed
// a USB channel already exists val channel = findEuiccChannelByPort(physicalSlotId, portId)!!
tryOpenUsbEuiccChannel()
usbChannel!!
} else {
// tryOpenEuiccChannel() will automatically dispose of invalid channels
// and recreate when needed
findEuiccChannelByPort(physicalSlotId, portId)!!
}
check(channel.valid) { "Invalid channel" } check(channel.valid) { "Invalid channel" }
break break
} catch (e: Exception) { } catch (e: Exception) {

View file

@ -16,11 +16,6 @@ interface EuiccChannel {
val valid: Boolean val valid: Boolean
/**
* Answer to Reset (ATR) value of the underlying interface, if any
*/
val atr: ByteArray?
/** /**
* Intrinsic name of this channel. For device-internal SIM slots, * Intrinsic name of this channel. For device-internal SIM slots,
* this should be null; for USB readers, this should be the name of * this should be null; for USB readers, this should be the name of

View file

@ -11,7 +11,7 @@ class EuiccChannelImpl(
override val type: String, override val type: String,
override val port: UiccPortInfoCompat, override val port: UiccPortInfoCompat,
override val intrinsicChannelName: String?, override val intrinsicChannelName: String?,
private val apduInterface: ApduInterface, apduInterface: ApduInterface,
verboseLoggingFlow: Flow<Boolean>, verboseLoggingFlow: Flow<Boolean>,
ignoreTLSCertificateFlow: Flow<Boolean> ignoreTLSCertificateFlow: Flow<Boolean>
) : EuiccChannel { ) : EuiccChannel {
@ -22,9 +22,6 @@ class EuiccChannelImpl(
override val lpa: LocalProfileAssistant = override val lpa: LocalProfileAssistant =
LocalProfileAssistantImpl(apduInterface, HttpInterfaceImpl(verboseLoggingFlow, ignoreTLSCertificateFlow)) LocalProfileAssistantImpl(apduInterface, HttpInterfaceImpl(verboseLoggingFlow, ignoreTLSCertificateFlow))
override val atr: ByteArray?
get() = (apduInterface as? ApduInterfaceAtrProvider)?.atr
override val valid: Boolean override val valid: Boolean
get() = lpa.valid get() = lpa.valid

View file

@ -33,8 +33,6 @@ class EuiccChannelWrapper(orig: EuiccChannel) : EuiccChannel {
get() = channel.valid get() = channel.valid
override val intrinsicChannelName: String? override val intrinsicChannelName: String?
get() = channel.intrinsicChannelName get() = channel.intrinsicChannelName
override val atr: ByteArray?
get() = channel.atr
override fun close() = channel.close() override fun close() = channel.close()

View file

@ -15,7 +15,7 @@ class OmapiApduInterface(
private val service: SEService, private val service: SEService,
private val port: UiccPortInfoCompat, private val port: UiccPortInfoCompat,
private val verboseLoggingFlow: Flow<Boolean> private val verboseLoggingFlow: Flow<Boolean>
): ApduInterface, ApduInterfaceAtrProvider { ): ApduInterface {
companion object { companion object {
const val TAG = "OmapiApduInterface" const val TAG = "OmapiApduInterface"
} }
@ -26,9 +26,6 @@ class OmapiApduInterface(
override val valid: Boolean override val valid: Boolean
get() = service.isConnected && (this::session.isInitialized && !session.isClosed) get() = service.isConnected && (this::session.isInitialized && !session.isClosed)
override val atr: ByteArray?
get() = session.atr
override fun connect() { override fun connect() {
session = service.getUiccReaderCompat(port.logicalSlotIndex + 1).openSession() session = service.getUiccReaderCompat(port.logicalSlotIndex + 1).openSession()
} }

View file

@ -3,7 +3,6 @@ package im.angry.openeuicc.core.usb
import android.hardware.usb.UsbDeviceConnection import android.hardware.usb.UsbDeviceConnection
import android.hardware.usb.UsbEndpoint import android.hardware.usb.UsbEndpoint
import android.util.Log import android.util.Log
import im.angry.openeuicc.core.ApduInterfaceAtrProvider
import im.angry.openeuicc.util.* import im.angry.openeuicc.util.*
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import net.typeblog.lpac_jni.ApduInterface import net.typeblog.lpac_jni.ApduInterface
@ -13,7 +12,7 @@ class UsbApduInterface(
private val bulkIn: UsbEndpoint, private val bulkIn: UsbEndpoint,
private val bulkOut: UsbEndpoint, private val bulkOut: UsbEndpoint,
private val verboseLoggingFlow: Flow<Boolean> private val verboseLoggingFlow: Flow<Boolean>
) : ApduInterface, ApduInterfaceAtrProvider { ): ApduInterface {
companion object { companion object {
private const val TAG = "UsbApduInterface" private const val TAG = "UsbApduInterface"
} }
@ -23,8 +22,6 @@ class UsbApduInterface(
private var channelId = -1 private var channelId = -1
override var atr: ByteArray? = null
override fun connect() { override fun connect() {
ccidDescription = UsbCcidDescription.fromRawDescriptors(conn.rawDescriptors)!! ccidDescription = UsbCcidDescription.fromRawDescriptors(conn.rawDescriptors)!!
@ -35,9 +32,7 @@ class UsbApduInterface(
transceiver = UsbCcidTransceiver(conn, bulkIn, bulkOut, ccidDescription, verboseLoggingFlow) transceiver = UsbCcidTransceiver(conn, bulkIn, bulkOut, ccidDescription, verboseLoggingFlow)
try { try {
// 6.1.1.1 PC_to_RDR_IccPowerOn (Page 20 of 40) transceiver.iccPowerOn()
// https://www.usb.org/sites/default/files/DWG_Smart-Card_USB-ICC_ICCD_rev10.pdf
atr = transceiver.iccPowerOn().data
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
throw e throw e

View file

@ -362,6 +362,9 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker {
) )
} }
val isForegroundTaskRunning: Boolean
get() = foregroundTaskState.value != ForegroundTaskState.Idle
suspend fun waitForForegroundTask() { suspend fun waitForForegroundTask() {
foregroundTaskState.takeWhile { it != ForegroundTaskState.Idle } foregroundTaskState.takeWhile { it != ForegroundTaskState.Idle }
.collect() .collect()
@ -445,7 +448,7 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker {
portId: Int, portId: Int,
iccid: String, iccid: String,
enable: Boolean, // Enable or disable the profile indicated in iccid enable: Boolean, // Enable or disable the profile indicated in iccid
reconnectTimeoutMillis: Long = 0 // 0 = do not wait for reconnect reconnectTimeoutMillis: Long = 0 // 0 = do not wait for reconnect, useful for USB readers
): ForegroundTaskSubscriberFlow = ): ForegroundTaskSubscriberFlow =
launchForegroundTask( launchForegroundTask(
getString(R.string.task_profile_switch), getString(R.string.task_profile_switch),

View file

@ -41,7 +41,7 @@ class EuiccInfoActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
@StringRes @StringRes
val titleResId: Int, val titleResId: Int,
val content: String?, val content: String?,
val copiedToastResId: Int? = null, val copiedToastResId: Int? = null
) )
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -114,7 +114,6 @@ class EuiccInfoActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
) )
) )
channel.lpa.euiccInfo2.let { info -> channel.lpa.euiccInfo2.let { info ->
add(Item(R.string.euicc_info_sgp22_version, info?.sgp22Version))
add(Item(R.string.euicc_info_firmware_version, info?.euiccFirmwareVersion)) add(Item(R.string.euicc_info_firmware_version, info?.euiccFirmwareVersion))
add(Item(R.string.euicc_info_globalplatform_version, info?.globalPlatformVersion)) add(Item(R.string.euicc_info_globalplatform_version, info?.globalPlatformVersion))
add(Item(R.string.euicc_info_pp_version, info?.ppVersion)) add(Item(R.string.euicc_info_pp_version, info?.ppVersion))
@ -134,13 +133,6 @@ class EuiccInfoActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
} }
add(Item(R.string.euicc_info_ci_type, getString(resId))) add(Item(R.string.euicc_info_ci_type, getString(resId)))
} }
add(
Item(
R.string.euicc_info_atr,
channel.atr?.encodeHex() ?: getString(R.string.unavailable),
copiedToastResId = R.string.toast_atr_copied,
)
)
} }
private fun formatByBoolean(b: Boolean, res: Pair<Int, Int>): String = private fun formatByBoolean(b: Boolean, res: Pair<Int, Int>): String =

View file

@ -228,7 +228,11 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
portId, portId,
iccid, iccid,
enable, enable,
reconnectTimeoutMillis = 30 * 1000 reconnectTimeoutMillis = if (isUsb) {
0
} else {
30 * 1000
}
).waitDone() ).waitDone()
when (err) { when (err) {

View file

@ -54,7 +54,6 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
profileRenameNewName.editText!!.setText(requireArguments().getString("currentName"))
toolbar.apply { toolbar.apply {
setTitle(R.string.rename) setTitle(R.string.rename)
setNavigationOnClickListener { setNavigationOnClickListener {
@ -67,6 +66,11 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment
} }
} }
override fun onStart() {
super.onStart()
profileRenameNewName.editText!!.setText(requireArguments().getString("currentName"))
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
setWidthPercent(95) setWidthPercent(95)

View file

@ -5,21 +5,15 @@ import android.view.View
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.Button import android.widget.Button
import android.widget.ProgressBar import android.widget.ProgressBar
import android.widget.Toast
import androidx.activity.OnBackPressedCallback import androidx.activity.OnBackPressedCallback
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import im.angry.openeuicc.common.R import im.angry.openeuicc.common.R
import im.angry.openeuicc.core.EuiccChannelManager
import im.angry.openeuicc.ui.BaseEuiccAccessActivity import im.angry.openeuicc.ui.BaseEuiccAccessActivity
import im.angry.openeuicc.util.* import im.angry.openeuicc.util.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import net.typeblog.lpac_jni.LocalProfileAssistant import net.typeblog.lpac_jni.LocalProfileAssistant
class DownloadWizardActivity: BaseEuiccAccessActivity() { class DownloadWizardActivity: BaseEuiccAccessActivity() {
@ -155,39 +149,13 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() {
private fun onNextPressed() { private fun onNextPressed() {
hideIme() hideIme()
nextButton.isEnabled = false if (currentFragment?.hasNext == true) {
progressBar.visibility = View.VISIBLE currentFragment?.beforeNext()
progressBar.isIndeterminate = true val nextFrag = currentFragment?.createNextFragment()
if (nextFrag == null) {
lifecycleScope.launch(Dispatchers.Main) { finish()
if (state.selectedLogicalSlot >= 0) { } else {
try { showFragment(nextFrag, R.anim.slide_in_right, R.anim.slide_out_left)
// This is run on IO by default
euiccChannelManager.withEuiccChannel(state.selectedLogicalSlot) { channel ->
// Be _very_ sure that the channel we got is valid
if (!channel.valid) throw EuiccChannelManager.EuiccChannelNotFoundException()
}
} catch (e: EuiccChannelManager.EuiccChannelNotFoundException) {
Toast.makeText(
this@DownloadWizardActivity,
R.string.download_wizard_slot_removed,
Toast.LENGTH_LONG
).show()
finish()
}
}
progressBar.visibility = View.GONE
nextButton.isEnabled = true
if (currentFragment?.hasNext == true) {
currentFragment?.beforeNext()
val nextFrag = currentFragment?.createNextFragment()
if (nextFrag == null) {
finish()
} else {
showFragment(nextFrag, R.anim.slide_in_right, R.anim.slide_out_left)
}
} }
} }
} }

View file

@ -22,6 +22,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp" android:layout_marginHorizontal="24dp"
android:layout_marginVertical="12dp" android:layout_marginVertical="12dp"
android:maxLines="1"
android:ellipsize="marquee"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/euicc_info_title" app:layout_constraintTop_toBottomOf="@id/euicc_info_title"

View file

@ -82,7 +82,7 @@
android:maxLines="1" android:maxLines="1"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:inputType="numberPassword" /> android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>

View file

@ -3,7 +3,6 @@
<string name="no_euicc">No removable eUICC card accessible by this app is detected on this device. Insert a compatible card or a USB reader.</string> <string name="no_euicc">No removable eUICC card accessible by this app is detected on this device. Insert a compatible card or a USB reader.</string>
<string name="no_profile">No profiles (yet) on this eSIM.</string> <string name="no_profile">No profiles (yet) on this eSIM.</string>
<string name="unknown">Unknown</string> <string name="unknown">Unknown</string>
<string name="unavailable">Unavailable</string>
<string name="help">Help</string> <string name="help">Help</string>
<string name="reload">Reload Slots</string> <string name="reload">Reload Slots</string>
@ -32,7 +31,6 @@
<string name="toast_profile_delete_confirm_text_mismatched">Confirmation string mismatch</string> <string name="toast_profile_delete_confirm_text_mismatched">Confirmation string mismatch</string>
<string name="toast_iccid_copied">ICCID copied to clipboard</string> <string name="toast_iccid_copied">ICCID copied to clipboard</string>
<string name="toast_eid_copied">EID copied to clipboard</string> <string name="toast_eid_copied">EID copied to clipboard</string>
<string name="toast_atr_copied">ATR copied to clipboard</string>
<string name="usb_permission">Grant USB permission</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_permission_needed">Permission is needed to access the USB smart card reader.</string>
@ -63,7 +61,6 @@
<string name="download_wizard">Download Wizard</string> <string name="download_wizard">Download Wizard</string>
<string name="download_wizard_back">Back</string> <string name="download_wizard_back">Back</string>
<string name="download_wizard_next">Next</string> <string name="download_wizard_next">Next</string>
<string name="download_wizard_slot_removed">Selected SIM has been removed</string>
<string name="download_wizard_slot_select">Select or confirm the eSIM you would like to download to:</string> <string name="download_wizard_slot_select">Select or confirm the eSIM you would like to download to:</string>
<string name="download_wizard_slot_type">Type:</string> <string name="download_wizard_slot_type">Type:</string>
<string name="download_wizard_slot_type_removable">Removable</string> <string name="download_wizard_slot_type_removable">Removable</string>
@ -124,7 +121,6 @@
<string name="euicc_info_access_mode">Access Mode</string> <string name="euicc_info_access_mode">Access Mode</string>
<string name="euicc_info_removable">Removable</string> <string name="euicc_info_removable">Removable</string>
<string name="euicc_info_eid" translatable="false">EID</string> <string name="euicc_info_eid" translatable="false">EID</string>
<string name="euicc_info_sgp22_version">SGP.22 Version</string>
<string name="euicc_info_firmware_version">eUICC OS Version</string> <string name="euicc_info_firmware_version">eUICC OS Version</string>
<string name="euicc_info_globalplatform_version">GlobalPlatform Version</string> <string name="euicc_info_globalplatform_version">GlobalPlatform Version</string>
<string name="euicc_info_sas_accreditation_number">SAS Accreditation Number</string> <string name="euicc_info_sas_accreditation_number">SAS Accreditation Number</string>
@ -134,7 +130,6 @@
<string name="euicc_info_ci_gsma_live">GSMA Live CI</string> <string name="euicc_info_ci_gsma_live">GSMA Live CI</string>
<string name="euicc_info_ci_gsma_test">GSMA Test CI</string> <string name="euicc_info_ci_gsma_test">GSMA Test CI</string>
<string name="euicc_info_ci_unknown">Unknown eSIM CI</string> <string name="euicc_info_ci_unknown">Unknown eSIM CI</string>
<string name="euicc_info_atr" translatable="false">Answer To Reset (ATR)</string>
<string name="yes">Yes</string> <string name="yes">Yes</string>
<string name="no">No</string> <string name="no">No</string>

View file

@ -2,7 +2,6 @@ package net.typeblog.lpac_jni
/* Corresponds to EuiccInfo2 in SGP.22 */ /* Corresponds to EuiccInfo2 in SGP.22 */
data class EuiccInfo2( data class EuiccInfo2(
val sgp22Version: String,
val profileVersion: String, val profileVersion: String,
val euiccFirmwareVersion: String, val euiccFirmwareVersion: String,
val globalPlatformVersion: String, val globalPlatformVersion: String,

View file

@ -62,7 +62,6 @@ internal object LpacJni {
external fun notificationsFree(head: Long) external fun notificationsFree(head: Long)
// EuiccInfo2 // EuiccInfo2
external fun euiccInfo2Free(info: Long) external fun euiccInfo2Free(info: Long)
external fun euiccInfo2GetSGP22Version(info: Long): String
external fun euiccInfo2GetProfileVersion(info: Long): String external fun euiccInfo2GetProfileVersion(info: Long): String
external fun euiccInfo2GetEuiccFirmwareVersion(info: Long): String external fun euiccInfo2GetEuiccFirmwareVersion(info: Long): String
external fun euiccInfo2GetGlobalPlatformVersion(info: Long): String external fun euiccInfo2GetGlobalPlatformVersion(info: Long): String

View file

@ -171,7 +171,6 @@ class LocalProfileAssistantImpl(
} }
val ret = EuiccInfo2( val ret = EuiccInfo2(
LpacJni.euiccInfo2GetSGP22Version(cInfo),
LpacJni.euiccInfo2GetProfileVersion(cInfo), LpacJni.euiccInfo2GetProfileVersion(cInfo),
LpacJni.euiccInfo2GetEuiccFirmwareVersion(cInfo), LpacJni.euiccInfo2GetEuiccFirmwareVersion(cInfo),
LpacJni.euiccInfo2GetGlobalPlatformVersion(cInfo), LpacJni.euiccInfo2GetGlobalPlatformVersion(cInfo),

View file

@ -266,7 +266,6 @@ void lpac_jni_euiccinfo2_free(struct es10c_ex_euiccinfo2 *info) {
LPAC_JNI_STRUCT_GETTER_NULL_TERM_LIST_NEXT(char*, stringArr) LPAC_JNI_STRUCT_GETTER_NULL_TERM_LIST_NEXT(char*, stringArr)
LPAC_JNI_STRUCT_FREE(struct es10c_ex_euiccinfo2, euiccInfo2, lpac_jni_euiccinfo2_free) LPAC_JNI_STRUCT_FREE(struct es10c_ex_euiccinfo2, euiccInfo2, lpac_jni_euiccinfo2_free)
LPAC_JNI_STRUCT_GETTER_STRING(struct es10c_ex_euiccinfo2, euiccInfo2, svn, SGP22Version)
LPAC_JNI_STRUCT_GETTER_STRING(struct es10c_ex_euiccinfo2, euiccInfo2, profileVersion, ProfileVersion) LPAC_JNI_STRUCT_GETTER_STRING(struct es10c_ex_euiccinfo2, euiccInfo2, profileVersion, ProfileVersion)
LPAC_JNI_STRUCT_GETTER_STRING(struct es10c_ex_euiccinfo2, euiccInfo2, euiccFirmwareVer, EuiccFirmwareVersion) LPAC_JNI_STRUCT_GETTER_STRING(struct es10c_ex_euiccinfo2, euiccInfo2, euiccFirmwareVer, EuiccFirmwareVersion)
LPAC_JNI_STRUCT_GETTER_STRING(struct es10c_ex_euiccinfo2, euiccInfo2, globalplatformVersion, GlobalPlatformVersion) LPAC_JNI_STRUCT_GETTER_STRING(struct es10c_ex_euiccinfo2, euiccInfo2, globalplatformVersion, GlobalPlatformVersion)