Compare commits

...

8 commits

Author SHA1 Message Date
1140ddb249 lpac-jni: IgnoreTLSCertificate -> AllowAllTrustManager 2024-12-08 11:15:11 -05:00
9be1ae7cd1 lpac-jni: Expose error reason enum as string 2024-12-08 11:14:10 -05:00
a7e97378fc Rework EuiccInfoActivity formatting 2024-12-08 10:51:14 -05:00
790cbb5a58 i18n: Update ja translations 2024-12-08 10:42:32 -05:00
2247749b37 ui: Set layout_constrainedWidth for profile title 2024-12-08 10:35:03 -05:00
249aea482b refactor: euicc info activity (#98)
improve maintainability

Co-authored-by: Peter Cai <peter@typeblog.net>
Reviewed-on: PeterCxy/OpenEUICC#98
Co-authored-by: septs <github@septs.pw>
Co-committed-by: septs <github@septs.pw>
2024-12-08 16:31:39 +01:00
dc0489a693 chore: simplify source code intent (#97)
Co-authored-by: Peter Cai <peter@typeblog.net>
Reviewed-on: PeterCxy/OpenEUICC#97
Co-authored-by: septs <github@septs.pw>
Co-committed-by: septs <github@septs.pw>
2024-12-08 16:30:00 +01:00
d3e54ece58 chore: improve compatibility check (#96)
Reviewed-on: PeterCxy/OpenEUICC#96
Co-authored-by: septs <github@septs.pw>
Co-committed-by: septs <github@septs.pw>
2024-12-08 16:29:05 +01:00
13 changed files with 116 additions and 112 deletions

View file

@ -15,12 +15,18 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import im.angry.openeuicc.common.R
import im.angry.openeuicc.core.EuiccChannel
import im.angry.openeuicc.util.*
import kotlinx.coroutines.launch
import net.typeblog.lpac_jni.impl.DEFAULT_PKID_GSMA_RSP2_ROOT_CI1
import net.typeblog.lpac_jni.impl.PKID_GSMA_TEST_CI
class EuiccInfoActivity : BaseEuiccAccessActivity() {
companion object {
private val YES_NO = Pair(R.string.yes, R.string.no)
private val SUPPORTED_UNSUPPORTED = Pair(R.string.supported, R.string.unsupported)
}
private lateinit var swipeRefresh: SwipeRefreshLayout
private lateinit var infoList: RecyclerView
@ -71,104 +77,59 @@ class EuiccInfoActivity : BaseEuiccAccessActivity() {
swipeRefresh.isRefreshing = true
lifecycleScope.launch {
val unknownStr = getString(R.string.unknown)
val newItems = mutableListOf<Pair<String, String>>()
newItems.add(
Pair(
getString(R.string.euicc_info_access_mode),
euiccChannelManager.withEuiccChannel(logicalSlotId) { channel -> channel.type }
)
)
newItems.add(
Pair(
getString(R.string.euicc_info_removable),
if (euiccChannelManager.withEuiccChannel(logicalSlotId) { channel -> channel.port.card.isRemovable }) {
getString(R.string.yes)
} else {
getString(R.string.no)
}
)
)
newItems.add(
Pair(
getString(R.string.euicc_info_eid),
euiccChannelManager.withEuiccChannel(logicalSlotId) { channel -> channel.lpa.eID }
)
)
val euiccInfo2 = euiccChannelManager.withEuiccChannel(logicalSlotId) { channel ->
channel.lpa.euiccInfo2
}
newItems.add(
Pair(
getString(R.string.euicc_info_firmware_version),
euiccInfo2?.euiccFirmwareVersion ?: unknownStr
)
)
newItems.add(
Pair(
getString(R.string.euicc_info_globalplatform_version),
euiccInfo2?.globalPlatformVersion ?: unknownStr
)
)
newItems.add(
Pair(
getString(R.string.euicc_info_pp_version),
euiccInfo2?.ppVersion ?: unknownStr
)
)
newItems.add(
Pair(
getString(R.string.euicc_info_sas_accreditation_number),
euiccInfo2?.sasAccreditationNumber ?: unknownStr
)
)
newItems.add(
Pair(
getString(R.string.euicc_info_free_nvram),
euiccInfo2?.freeNvram?.let { formatFreeSpace(it) } ?: unknownStr
))
newItems.add(
Pair(
getString(R.string.euicc_info_gsma_prod),
if (euiccInfo2?.euiccCiPKIdListForSigning?.contains(
DEFAULT_PKID_GSMA_RSP2_ROOT_CI1
) == true
) {
getString(R.string.supported)
} else {
getString(R.string.unsupported)
}
)
)
newItems.add(
Pair(
getString(R.string.euicc_info_gsma_test),
if (PKID_GSMA_TEST_CI.any { euiccInfo2?.euiccCiPKIdListForSigning?.contains(it) == true }) {
getString(R.string.supported)
} else {
getString(R.string.unsupported)
}
)
)
(infoList.adapter!! as EuiccInfoAdapter).euiccInfoItems = newItems
(infoList.adapter!! as EuiccInfoAdapter).euiccInfoItems =
euiccChannelManager.withEuiccChannel(logicalSlotId, ::buildPairs).map {
Pair(getString(it.first), it.second ?: getString(R.string.unknown))
}
swipeRefresh.isRefreshing = false
}
}
private fun buildPairs(channel: EuiccChannel) = buildList {
add(Pair(R.string.euicc_info_access_mode, channel.type))
add(
Pair(
R.string.euicc_info_removable,
formatByBoolean(channel.port.card.isRemovable, YES_NO)
)
)
add(Pair(R.string.euicc_info_eid, channel.lpa.eID))
channel.lpa.euiccInfo2.let { info ->
add(Pair(R.string.euicc_info_firmware_version, info?.euiccFirmwareVersion))
add(Pair(R.string.euicc_info_globalplatform_version, info?.globalPlatformVersion))
add(Pair(R.string.euicc_info_pp_version, info?.ppVersion))
add(Pair(R.string.euicc_info_sas_accreditation_number, info?.sasAccreditationNumber))
add(Pair(R.string.euicc_info_free_nvram, info?.freeNvram?.let(::formatFreeSpace)))
}
channel.lpa.euiccInfo2?.euiccCiPKIdListForSigning.orEmpty().let { signers ->
add(
Pair(
R.string.euicc_info_gsma_prod,
formatByBoolean(
signers.contains(DEFAULT_PKID_GSMA_RSP2_ROOT_CI1),
SUPPORTED_UNSUPPORTED
)
)
)
add(
Pair(
R.string.euicc_info_gsma_test,
formatByBoolean(PKID_GSMA_TEST_CI.any(signers::contains), SUPPORTED_UNSUPPORTED)
)
)
}
}
private fun formatByBoolean(b: Boolean, res: Pair<Int, Int>): String =
getString(
if (b) {
res.first
} else {
res.second
}
)
inner class EuiccInfoViewHolder(root: View) : ViewHolder(root) {
private val title: TextView = root.requireViewById(R.id.euicc_info_title)
private val content: TextView = root.requireViewById(R.id.euicc_info_content)

View file

@ -47,10 +47,6 @@ class SettingsFragment: PreferenceFragmentCompat() {
setOnPreferenceClickListener(::onAppVersionClicked)
}
findPreference<Preference>("pref_info_source_code")?.apply {
intent = Intent(Intent.ACTION_VIEW, Uri.parse(summary.toString()))
}
findPreference<Preference>("pref_language")?.apply {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return@apply
isVisible = true

View file

@ -28,7 +28,8 @@
app:layout_constraintRight_toLeftOf="@+id/profile_menu"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/state"
app:layout_constraintHorizontal_bias="0" />
app:layout_constraintHorizontal_bias="0"
app:layout_constrainedWidth="true" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/profile_menu"

View file

@ -136,4 +136,8 @@
<string name="pref_language_desc">アプリの言語を選択</string>
<string name="pref_developer_unfiltered_profile_list">すべてのプロファイルを表示</string>
<string name="pref_developer_unfiltered_profile_list_desc">プロダクション以外のプロファイルも表示する</string>
<string name="profile_class">タイプ:</string>
<string name="profile_class_testing">テスティング</string>
<string name="profile_class_provisioning">準備中</string>
<string name="profile_class_operational">動作中</string>
</resources>

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<im.angry.openeuicc.ui.preference.LongSummaryPreferenceCategory
app:title="@string/pref_notifications"
app:summary="@string/pref_notifications_desc"
@ -88,6 +89,10 @@
app:iconSpaceReserved="false"
app:title="@string/pref_info_source_code"
app:summary="@string/pref_info_source_code_url"
app:key="pref_info_source_code"/>
app:key="pref_info_source_code">
<intent
android:action="android.intent.action.VIEW"
android:data="@string/pref_info_source_code_url" />
</Preference>
</PreferenceCategory>
</PreferenceScreen>

View file

@ -1,6 +1,8 @@
package im.angry.openeuicc.ui
import android.annotation.SuppressLint
import android.os.Bundle
import android.text.Html
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
@ -8,6 +10,7 @@ import android.widget.TextView
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.children
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
@ -29,15 +32,16 @@ class CompatibilityCheckActivity: AppCompatActivity() {
setupToolbarInsets()
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
compatibilityCheckList = requireViewById(R.id.recycler_view)
compatibilityCheckList.layoutManager =
LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
compatibilityCheckList.addItemDecoration(DividerItemDecoration(this, LinearLayoutManager.VERTICAL))
compatibilityCheckList.adapter = adapter
compatibilityCheckList = requireViewById<RecyclerView>(R.id.recycler_view).also {
it.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
it.addItemDecoration(DividerItemDecoration(this, LinearLayoutManager.VERTICAL))
it.adapter = adapter
}
setupRootViewInsets(compatibilityCheckList)
}
@SuppressLint("NotifyDataSetChanged")
override fun onStart() {
super.onStart()
lifecycleScope.launch {
@ -61,10 +65,10 @@ class CompatibilityCheckActivity: AppCompatActivity() {
fun bindItem(item: CompatibilityCheck) {
titleView.text = item.title
descView.text = item.description
descView.text = Html.fromHtml(item.description, Html.FROM_HTML_MODE_COMPACT)
statusContainer.children.forEach {
it.visibility = View.GONE
it.isVisible = false
}
val viewId = when (item.state) {
@ -73,7 +77,7 @@ class CompatibilityCheckActivity: AppCompatActivity() {
CompatibilityCheck.State.FAILURE_UNKNOWN -> R.id.compatibility_check_unknown
else -> R.id.compatibility_check_progress_bar
}
root.requireViewById<View>(viewId).visibility = View.VISIBLE
root.requireViewById<View>(viewId).isVisible = true
}
}

View file

@ -12,11 +12,11 @@
<string name="compatibility_check_omapi_connectivity">OMAPI Connectivity</string>
<string name="compatibility_check_omapi_connectivity_desc">Does your device allow access to Secure Elements on SIM cards via OMAPI?</string>
<string name="compatibility_check_omapi_connectivity_fail">Unable to detect Secure Element readers for SIM cards via OMAPI. If you have not inserted a SIM in this device, try inserting one and retry this check.</string>
<string name="compatibility_check_omapi_connectivity_partial_success_sim_number">Successfully detected Secure Element access, but only for the following SIM slots: <b>SIM%s</b>.</string>
<string name="compatibility_check_omapi_connectivity_partial_success_sim_number">Successfully detected Secure Element access, but only for the following SIM slots: &lt;b&gt;SIM%s&lt;/b&gt;.</string>
<string name="compatibility_check_isdr_channel">ISD-R Channel Access</string>
<string name="compatibility_check_isdr_channel_desc">Does your device support opening an ISD-R (management) channel to eSIMs via OMAPI?</string>
<string name="compatibility_check_isdr_channel_desc_unknown">Cannot determine whether ISD-R access through OMAPI is supported. You might want to retry with SIM cards inserted (any SIM card will do) if not already.</string>
<string name="compatibility_check_isdr_channel_desc_partial_fail">OMAPI access to ISD-R is only possible on the following SIM slots: <b>SIM%s</b>.</string>
<string name="compatibility_check_isdr_channel_desc_partial_fail">OMAPI access to ISD-R is only possible on the following SIM slots: &lt;b&gt;SIM%s&lt;/b&gt;.</string>
<string name="compatibility_check_known_broken">Not on the Known Broken List</string>
<string name="compatibility_check_known_broken_desc">Making sure your device is not known to have bugs associated with removable eSIMs.</string>
<string name="compatibility_check_known_broken_fail">Oops, your device is known to have bugs when accessing removable eSIMs. This does not necessarily mean that it will not work at all, but you will have to proceed with caution.</string>

View file

@ -5,6 +5,7 @@ import net.typeblog.lpac_jni.HttpInterface.HttpResponse
interface LocalProfileAssistant {
@Suppress("ArrayInDataClass")
data class ProfileDownloadException(
val lpaErrorReason: String,
val lastHttpResponse: HttpResponse?,
val lastHttpException: Exception?,
val lastApduResponse: ByteArray?,

View file

@ -29,6 +29,7 @@ internal object LpacJni {
// We do not expose all of the functions because of tediousness :)
external fun downloadProfile(handle: Long, smdp: String, matchingId: String?, imei: String?,
confirmationCode: String?, callback: ProfileDownloadCallback): Int
external fun downloadErrCodeToString(code: Int): String
external fun handleNotification(handle: Long, seqNumber: Long): Int
// Cancel any ongoing es9p and/or es10b sessions
external fun cancelSessions(handle: Long)

View file

@ -5,7 +5,7 @@ import java.security.cert.X509Certificate
import javax.net.ssl.X509TrustManager
@SuppressLint("CustomX509TrustManager")
class IgnoreTLSCertificate : X509TrustManager {
class AllowAllTrustManager : X509TrustManager {
@SuppressLint("TrustAllX509TrustManager")
override fun checkClientTrusted(p0: Array<out X509Certificate>?, p1: String?) {
return

View file

@ -83,7 +83,7 @@ class HttpInterfaceImpl(
private fun getSocketFactory(): SSLSocketFactory {
val trustManagers =
if (runBlocking { ignoreTLSCertificateFlow.first() }) {
arrayOf(IgnoreTLSCertificate())
arrayOf(AllowAllTrustManager())
} else {
this.trustManagers
}

View file

@ -214,6 +214,7 @@ class LocalProfileAssistantImpl(
if (res != 0) {
// Construct the error now to store any error information we _can_ access
val err = LocalProfileAssistant.ProfileDownloadException(
lpaErrorReason = LpacJni.downloadErrCodeToString(-res),
httpInterface.lastHttpResponse,
httpInterface.lastHttpException,
apduInterface.lastApduResponse,

View file

@ -148,4 +148,34 @@ Java_net_typeblog_lpac_1jni_LpacJni_cancelSessions(JNIEnv *env, jobject thiz, jl
es9p_cancel_session(ctx);
es10b_cancel_session(ctx, ES10B_CANCEL_SESSION_REASON_UNDEFINED);
euicc_http_cleanup(ctx);
}
#define QUOTE(S) #S
#define ERRCODE_ENUM_TO_STRING(VARIANT) case VARIANT: return toJString(env, QUOTE(VARIANT))
JNIEXPORT jstring JNICALL
Java_net_typeblog_lpac_1jni_LpacJni_downloadErrCodeToString(JNIEnv *env, jobject thiz, jint code) {
switch (code) {
ERRCODE_ENUM_TO_STRING(ES10B_ERROR_REASON_INCORRECT_INPUT_VALUES);
ERRCODE_ENUM_TO_STRING(ES10B_ERROR_REASON_INVALID_SIGNATURE);
ERRCODE_ENUM_TO_STRING(ES10B_ERROR_REASON_INVALID_TRANSACTION_ID);
ERRCODE_ENUM_TO_STRING(ES10B_ERROR_REASON_UNSUPPORTED_CRT_VALUES);
ERRCODE_ENUM_TO_STRING(ES10B_ERROR_REASON_UNSUPPORTED_REMOTE_OPERATION_TYPE);
ERRCODE_ENUM_TO_STRING(ES10B_ERROR_REASON_UNSUPPORTED_PROFILE_CLASS);
ERRCODE_ENUM_TO_STRING(ES10B_ERROR_REASON_SCP03T_STRUCTURE_ERROR);
ERRCODE_ENUM_TO_STRING(ES10B_ERROR_REASON_SCP03T_SECURITY_ERROR);
ERRCODE_ENUM_TO_STRING(
ES10B_ERROR_REASON_INSTALL_FAILED_DUE_TO_ICCID_ALREADY_EXISTS_ON_EUICC);
ERRCODE_ENUM_TO_STRING(
ES10B_ERROR_REASON_INSTALL_FAILED_DUE_TO_INSUFFICIENT_MEMORY_FOR_PROFILE);
ERRCODE_ENUM_TO_STRING(ES10B_ERROR_REASON_INSTALL_FAILED_DUE_TO_INTERRUPTION);
ERRCODE_ENUM_TO_STRING(ES10B_ERROR_REASON_INSTALL_FAILED_DUE_TO_PE_PROCESSING_ERROR);
ERRCODE_ENUM_TO_STRING(ES10B_ERROR_REASON_INSTALL_FAILED_DUE_TO_ICCID_MISMATCH);
ERRCODE_ENUM_TO_STRING(
ES10B_ERROR_REASON_TEST_PROFILE_INSTALL_FAILED_DUE_TO_INVALID_NAA_KEY);
ERRCODE_ENUM_TO_STRING(ES10B_ERROR_REASON_PPR_NOT_ALLOWED);
ERRCODE_ENUM_TO_STRING(ES10B_ERROR_REASON_INSTALL_FAILED_DUE_TO_UNKNOWN_ERROR);
default:
return toJString(env, "ES10B_ERROR_REASON_UNDEFINED");
}
}