Compare commits

...

2 commits

Author SHA1 Message Date
Peter Cai 9b75295936 CompatibilityCheck: show unknown status when "secure element is not present"
Some checks failed
/ build-debug (push) Failing after 3m35s
Some devices "optimize" their OMAPI by reporting this status when both
slots are empty. Even just inserting one SIM would fix this error for
both slots.

In this case, we should not imply that the device is incompatible.
2024-02-19 16:58:33 -05:00
Peter Cai 048764d305 refactor: Comaptibility checks should return the success / failure state directly 2024-02-19 16:42:39 -05:00
4 changed files with 49 additions and 27 deletions

View file

@ -64,6 +64,9 @@ class CompatibilityCheckActivity: AppCompatActivity() {
CompatibilityCheck.State.FAILURE -> {
root.findViewById<View>(R.id.compatibility_check_error).visibility = View.VISIBLE
}
CompatibilityCheck.State.FAILURE_UNKNOWN -> {
root.findViewById<View>(R.id.compatibility_check_unknown).visibility = View.VISIBLE
}
else -> {
root.findViewById<View>(R.id.compatibility_check_progress_bar).visibility = View.VISIBLE
}

View file

@ -9,6 +9,7 @@ import im.angry.easyeuicc.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import java.io.IOException
fun getCompatibilityChecks(context: Context): List<CompatibilityCheck> =
listOf(
@ -38,6 +39,7 @@ abstract class CompatibilityCheck(context: Context) {
NOT_STARTED,
IN_PROGRESS,
SUCCESS,
FAILURE_UNKNOWN, // The check technically failed, but no conclusion can be drawn
FAILURE
}
@ -49,21 +51,17 @@ abstract class CompatibilityCheck(context: Context) {
val description: String
get() = when {
state == State.FAILURE && this::failureDescription.isInitialized -> failureDescription
(state == State.FAILURE || state == State.FAILURE_UNKNOWN) && this::failureDescription.isInitialized -> failureDescription
else -> defaultDescription
}
protected abstract suspend fun doCheck(): Boolean
protected abstract suspend fun doCheck(): State
suspend fun run() {
state = State.IN_PROGRESS
delay(200)
state = try {
if (doCheck()) {
State.SUCCESS
} else {
State.FAILURE
}
doCheck()
} catch (_: Exception) {
State.FAILURE
}
@ -76,10 +74,10 @@ internal class HasSystemFeaturesCheck(private val context: Context): Compatibili
override val defaultDescription: String
get() = context.getString(R.string.compatibility_check_system_features_desc)
override suspend fun doCheck(): Boolean {
override suspend fun doCheck(): State {
if (!context.packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
failureDescription = context.getString(R.string.compatibility_check_system_features_no_telephony)
return false
return State.FAILURE
}
// We can check OMAPI UICC availability on R or later (if before R, we check OMAPI connectivity later)
@ -87,10 +85,10 @@ internal class HasSystemFeaturesCheck(private val context: Context): Compatibili
PackageManager.FEATURE_SE_OMAPI_UICC
)) {
failureDescription = context.getString(R.string.compatibility_check_system_features_no_omapi)
return false
return State.FAILURE
}
return true
return State.SUCCESS
}
}
@ -100,25 +98,25 @@ internal class OmapiConnCheck(private val context: Context): CompatibilityCheck(
override val defaultDescription: String
get() = context.getString(R.string.compatibility_check_omapi_connectivity_desc)
override suspend fun doCheck(): Boolean {
override suspend fun doCheck(): State {
val seService = connectSEService(context)
if (!seService.isConnected) {
failureDescription = context.getString(R.string.compatibility_check_omapi_connectivity_fail)
return false
return State.FAILURE
}
val tm = context.getSystemService(TelephonyManager::class.java)
val simReaders = seService.readers.filter { it.isSIM }
if (simReaders.isEmpty()) {
failureDescription = context.getString(R.string.compatibility_check_omapi_connectivity_fail)
return false
return State.FAILURE
} else if (simReaders.size < tm.activeModemCountCompat) {
failureDescription = context.getString(R.string.compatibility_check_omapi_connectivity_fail_sim_number,
simReaders.map { it.slotIndex }.joinToString(", "))
return false
return State.FAILURE
}
return true
return State.SUCCESS
}
}
@ -132,30 +130,38 @@ internal class IsdrChannelAccessCheck(private val context: Context): Compatibili
override val defaultDescription: String
get() = context.getString(R.string.compatibility_check_isdr_channel_desc)
override suspend fun doCheck(): Boolean {
override suspend fun doCheck(): State {
val seService = connectSEService(context)
val (validSlotIds, result) = seService.readers.filter { it.isSIM }.map {
try {
it.openSession().openLogicalChannel(ISDR_AID)?.close()
Pair(it.slotIndex, true)
Pair(it.slotIndex, State.SUCCESS)
} catch (_: SecurityException) {
// Ignore; this is expected when everything works
// ref: https://android.googlesource.com/platform/frameworks/base/+/4fe64fb4712a99d5da9c9a0eb8fd5169b252e1e1/omapi/java/android/se/omapi/Session.java#305
// SecurityException is only thrown when Channel is constructed, which means everything else needs to succeed
Pair(it.slotIndex, true)
Pair(it.slotIndex, State.SUCCESS)
} catch (e: IOException) {
e.printStackTrace()
if (e.message?.contains("Secure Element is not present") == true) {
failureDescription = context.getString(R.string.compatibility_check_isdr_channel_desc_unknown)
Pair(it.slotIndex, State.FAILURE_UNKNOWN)
} else {
Pair(it.slotIndex, State.FAILURE)
}
} catch (e: Exception) {
e.printStackTrace()
Pair(it.slotIndex, false)
Pair(it.slotIndex, State.FAILURE)
}
}.fold(Pair(mutableListOf<Int>(), true)) { (ids, result), (id, ok) ->
if (!ok) {
Pair(ids, false)
}.fold(Pair(mutableListOf<Int>(), State.SUCCESS)) { (ids, result), (id, ok) ->
if (ok != State.SUCCESS) {
Pair(ids, ok)
} else {
Pair(ids.apply { add(id) }, result)
}
}
if (!result && validSlotIds.size > 0) {
if (result != State.SUCCESS && validSlotIds.size > 0) {
if (!context.packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_EUICC)) {
failureDescription = context.getString(
R.string.compatibility_check_isdr_channel_desc_partial_fail,
@ -164,7 +170,7 @@ internal class IsdrChannelAccessCheck(private val context: Context): Compatibili
} else {
// If the device has embedded eSIMs, we can likely ignore the failure here;
// the OMAPI failure likely resulted from trying to access internal eSIMs.
return true
return State.SUCCESS
}
}
@ -186,6 +192,10 @@ internal class KnownBrokenCheck(private val context: Context): CompatibilityChec
failureDescription = context.getString(R.string.compatibility_check_known_broken_fail)
}
override suspend fun doCheck(): Boolean =
Build.MANUFACTURER.lowercase() !in BROKEN_MANUFACTURERS
override suspend fun doCheck(): State =
if (Build.MANUFACTURER.lowercase() in BROKEN_MANUFACTURERS) {
State.FAILURE
} else {
State.SUCCESS
}
}

View file

@ -60,6 +60,14 @@
android:layout_width="32dp"
android:layout_height="32dp" />
<ImageView
android:id="@+id/compatibility_check_unknown"
android:src="@drawable/ic_question_outline"
android:visibility="gone"
android:layout_gravity="center"
android:layout_width="32dp"
android:layout_height="32dp" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -13,6 +13,7 @@
<string name="compatibility_check_omapi_connectivity_fail_sim_number">Only the following SIM slots are accessible via OMAPI: %s.</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: %s.</string>
<string name="compatibility_check_known_broken">Known Broken?</string>
<string name="compatibility_check_known_broken_desc">Making sure your device is not known to have bugs associated with removable eSIMs.</string>