Introducing EasyEUICC, the unprivileged version of OpenEUICC

This commit is contained in:
Peter Cai 2023-12-28 21:21:37 -05:00
parent bbfffe3abf
commit 554b43b101
33 changed files with 481 additions and 96 deletions

View file

@ -4,6 +4,7 @@
<bytecodeTargetLevel target="1.7">
<module name="OpenEUICC.app" target="17" />
<module name="OpenEUICC.app-common" target="17" />
<module name="OpenEUICC.app-unpriv" target="17" />
<module name="OpenEUICC.libs.hidden-apis-shim" target="17" />
<module name="OpenEUICC.libs.lpac-jni" target="17" />
</bytecodeTargetLevel>

View file

@ -14,6 +14,7 @@
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
<option value="$PROJECT_DIR$/app-common" />
<option value="$PROJECT_DIR$/app-unpriv" />
<option value="$PROJECT_DIR$/libs" />
<option value="$PROJECT_DIR$/libs/hidden-apis-shim" />
<option value="$PROJECT_DIR$/libs/hidden-apis-stub" />

View file

@ -4,14 +4,11 @@ import im.angry.openeuicc.util.*
import net.typeblog.lpac_jni.LocalProfileAssistant
abstract class EuiccChannel(
port: UiccPortInfoCompat
val port: UiccPortInfoCompat
) {
val slotId = port.card.physicalSlotIndex // PHYSICAL slot
val logicalSlotId = port.logicalSlotIndex
val portId = port.portIndex
val cardId = port.card.cardId
val removable = port.card.isRemovable
val isMEP = port.card.isMultipleEnabledProfilesSupported
abstract val lpa: LocalProfileAssistant
val valid: Boolean

View file

@ -34,7 +34,8 @@ open class EuiccChannelManager(protected val context: Context) {
private val handler = Handler(HandlerThread("BaseEuiccChannelManager").also { it.start() }.looper)
protected open fun checkPrivileges() = tm.hasCarrierPrivileges()
protected open val uiccCards: Collection<UiccCardInfoCompat>
get() = (0..<tm.activeModemCount).map { FakeUiccCardInfoCompat(it) }
private suspend fun connectSEService(): SEService = suspendCoroutine { cont ->
handler.post {
@ -107,9 +108,8 @@ open class EuiccChannelManager(protected val context: Context) {
fun findEuiccChannelBySlotBlocking(logicalSlotId: Int): EuiccChannel? =
runBlocking {
if (!checkPrivileges()) return@runBlocking null
withContext(Dispatchers.IO) {
for (card in tm.uiccCardsInfoCompat) {
for (card in uiccCards) {
for (port in card.ports) {
if (port.logicalSlotIndex == logicalSlotId) {
return@withContext tryOpenEuiccChannel(port)
@ -122,9 +122,8 @@ open class EuiccChannelManager(protected val context: Context) {
}
fun findEuiccChannelByPhysicalSlotBlocking(physicalSlotId: Int): EuiccChannel? = runBlocking {
if (!checkPrivileges()) return@runBlocking null
withContext(Dispatchers.IO) {
for (card in tm.uiccCardsInfoCompat) {
for (card in uiccCards) {
if (card.physicalSlotIndex != physicalSlotId) continue
for (port in card.ports) {
tryOpenEuiccChannel(port)?.let { return@withContext it }
@ -136,8 +135,7 @@ open class EuiccChannelManager(protected val context: Context) {
}
fun findAllEuiccChannelsByPhysicalSlotBlocking(physicalSlotId: Int): List<EuiccChannel>? = runBlocking {
if (!checkPrivileges()) return@runBlocking null
for (card in tm.uiccCardsInfoCompat) {
for (card in uiccCards) {
if (card.physicalSlotIndex != physicalSlotId) continue
return@runBlocking card.ports.mapNotNull { tryOpenEuiccChannel(it) }
.ifEmpty { null }
@ -146,21 +144,18 @@ open class EuiccChannelManager(protected val context: Context) {
}
fun findEuiccChannelByPortBlocking(physicalSlotId: Int, portId: Int): EuiccChannel? = runBlocking {
if (!checkPrivileges()) return@runBlocking null
withContext(Dispatchers.IO) {
tm.uiccCardsInfoCompat.find { it.physicalSlotIndex == physicalSlotId }?.let { card ->
uiccCards.find { it.physicalSlotIndex == physicalSlotId }?.let { card ->
card.ports.find { it.portIndex == portId }?.let { tryOpenEuiccChannel(it) }
}
}
}
suspend fun enumerateEuiccChannels() {
if (!checkPrivileges()) return
withContext(Dispatchers.IO) {
ensureSEService()
for (uiccInfo in tm.uiccCardsInfoCompat) {
for (uiccInfo in uiccCards) {
for (port in uiccInfo.ports) {
if (tryOpenEuiccChannel(port) != null) {
Log.d(TAG, "Found eUICC on slot ${uiccInfo.physicalSlotIndex} port ${port.portIndex}")
@ -174,8 +169,6 @@ open class EuiccChannelManager(protected val context: Context) {
get() = channels.toList()
fun invalidate() {
if (!checkPrivileges()) return
for (channel in channels) {
channel.close()
}

View file

@ -1,83 +1,41 @@
package im.angry.openeuicc.util
import android.annotation.SuppressLint
import android.os.Build
import android.telephony.TelephonyManager
import android.telephony.UiccCardInfo
import android.telephony.UiccPortInfo
import im.angry.openeuicc.util.*
import java.lang.RuntimeException
@Suppress("DEPRECATION")
class UiccCardInfoCompat(val inner: UiccCardInfo) {
/*
* In the privileged version, the EuiccChannelManager should work
* based on real Uicc{Card,Port}Info reported by TelephonyManager.
* However, when unprivileged, we cannot depend on the fact that
* we can access TelephonyManager. ARA-M only grants access to
* OMAPI, but not TelephonyManager APIs that are associated with
* carrier privileges.
*
* To maximally share code between the two variants, we define
* an interface of whatever information will be used in the shared
* portion of EuiccChannelManager etc. When unprivileged, we
* generate "fake" versions based solely on how many slots the phone
* has, while the privileged version can populate the fields with
* real information, extending whenever needed.
*/
interface UiccCardInfoCompat {
val physicalSlotIndex: Int
get() =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
inner.physicalSlotIndex
} else {
inner.slotIndex
}
val ports: Collection<UiccPortInfoCompat>
get() =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
inner.ports.map { UiccPortInfoCompat(it, this) }
} else {
listOf(UiccPortInfoCompat(null, this))
}
val isEuicc: Boolean
get() = inner.isEuicc
val isRemovable: Boolean
get() = inner.isRemovable
val isMultipleEnabledProfilesSupported: Boolean
get() =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
inner.isMultipleEnabledProfilesSupported
} else {
false
}
val cardId: Int
get() = inner.cardId
}
class UiccPortInfoCompat(private val _inner: Any?, val card: UiccCardInfoCompat) {
init {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
check(_inner != null && _inner is UiccPortInfo) {
"_inner is not UiccPortInfo on TIRAMISU"
}
}
}
val inner: UiccPortInfo
get() =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
_inner as UiccPortInfo
} else {
throw RuntimeException("UiccPortInfo does not exist before T")
}
interface UiccPortInfoCompat {
val card: UiccCardInfoCompat
val portIndex: Int
get() =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
inner.portIndex
} else {
0
}
val logicalSlotIndex: Int
get() =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
inner.logicalSlotIndex
} else {
card.physicalSlotIndex // logical is the same as physical below TIRAMISU
}
}
val TelephonyManager.uiccCardsInfoCompat: List<UiccCardInfoCompat>
@SuppressLint("MissingPermission")
get() = uiccCardsInfo.map { UiccCardInfoCompat(it) }
data class FakeUiccCardInfoCompat(
override val physicalSlotIndex: Int,
): UiccCardInfoCompat {
override val ports: Collection<UiccPortInfoCompat> =
listOf(FakeUiccPortInfoCompat(this))
}
data class FakeUiccPortInfoCompat(
override val card: UiccCardInfoCompat
): UiccPortInfoCompat {
override val portIndex: Int = 0
override val logicalSlotIndex: Int = card.physicalSlotIndex
}

View file

@ -5,4 +5,7 @@
<color name="pink_800">#AD1457</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="brand">@color/pink_600</color>
<color name="brandSecondary">@color/pink_800</color>
</resources>

View file

@ -6,8 +6,8 @@
<item name="colorPrimaryVariant">@color/gray_300</item>
<item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/pink_600</item>
<item name="colorSecondaryVariant">@color/pink_800</item>
<item name="colorSecondary">@color/brand</item>
<item name="colorSecondaryVariant">@color/brandSecondary</item>
<item name="colorOnSecondary">@color/white</item>
<item name="colorAccent">?attr/colorSecondary</item>
<!-- Status bar color. -->

1
app-unpriv/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

62
app-unpriv/build.gradle Normal file
View file

@ -0,0 +1,62 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
apply from: '../helpers.gradle'
def keystorePropertiesFile = rootProject.file("keystore.properties");
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
android {
namespace 'im.angry.easyeuicc'
compileSdk 34
defaultConfig {
applicationId "im.angry.easyeuicc"
minSdk 30
targetSdk 34
versionCode getGitVersionCode()
versionName getGitVersionName()
}
signingConfigs {
config {
storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
keyAlias keystoreProperties['unprivKeyAlias']
keyPassword keystoreProperties['unprivKeyPassword']
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.config
}
debug {
debuggable false
signingConfig signingConfigs.config
}
}
applicationVariants.configureEach { variant ->
if (variant.name == "debug") {
variant.outputs.each { o -> o.versionCodeOverride = System.currentTimeSeconds() }
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation project(":app-common")
implementation 'androidx.core:core-ktx:1.9.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.11.0'
}

21
app-unpriv/proguard-rules.pro vendored Normal file
View file

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:name="im.angry.openeuicc.OpenEuiccApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.OpenEUICC">
<activity
android:name="im.angry.openeuicc.ui.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View file

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#9C27B0"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View file

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF">
<group android:scaleX="0.5162"
android:scaleY="0.5162"
android:translateX="5.8056"
android:translateY="5.8056">
<path
android:fillColor="@android:color/white"
android:pathData="M19.99,4c0,-1.1 -0.89,-2 -1.99,-2h-8L4,8v12c0,1.1 0.9,2 2,2h12.01c1.1,0 1.99,-0.9 1.99,-2l-0.01,-16zM9,19L7,19v-2h2v2zM17,19h-2v-2h2v2zM9,15L7,15v-4h2v4zM13,19h-2v-4h2v4zM13,13h-2v-2h2v2zM17,15h-2v-4h2v4z"/>
</group>
</vector>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_500">#9C27B0</color>
<color name="purple_700">#7B1FA2</color>
<color name="brand">@color/purple_500</color>
<color name="brandSecondary">@color/purple_700</color>
</resources>

View file

@ -0,0 +1,3 @@
<resources>
<string name="app_name" translatable="false">EasyEUICC</string>
</resources>

View file

@ -0,0 +1,17 @@
package im.angry.easyeuicc
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}

View file

@ -8,9 +8,11 @@ import java.lang.Exception
import java.lang.IllegalArgumentException
class PrivilegedEuiccChannelManager(context: Context): EuiccChannelManager(context) {
override fun checkPrivileges() = true // TODO: Implement proper system app check
override val uiccCards: Collection<UiccCardInfoCompat>
get() = tm.uiccCardsInfoCompat
override fun tryOpenEuiccChannelPrivileged(port: UiccPortInfoCompat): EuiccChannel? {
override fun tryOpenEuiccChannelPrivileged(_port: UiccPortInfoCompat): EuiccChannel? {
val port = _port as RealUiccPortInfoCompat
if (port.card.isRemovable) {
// Attempt unprivileged (OMAPI) before TelephonyManager
// but still try TelephonyManager in case OMAPI is broken

View file

@ -4,6 +4,7 @@ import android.view.View
import android.view.ViewGroup
import android.widget.Button
import im.angry.openeuicc.R
import im.angry.openeuicc.util.*
class PrivilegedEuiccManagementFragment: EuiccManagementFragment() {
companion object {

View file

@ -1,8 +1,93 @@
package im.angry.openeuicc.util
import android.annotation.SuppressLint
import android.os.Build
import android.telephony.IccOpenLogicalChannelResponse
import android.telephony.TelephonyManager
import android.telephony.UiccCardInfo
import android.telephony.UiccPortInfo
import java.lang.RuntimeException
/*
* Implementation of Uicc{Card,Port}InfoCompat when privileged.
* Also handles compatibility with different platform API versions.
*/
@Suppress("DEPRECATION")
class RealUiccCardInfoCompat(val inner: UiccCardInfo): UiccCardInfoCompat {
override val physicalSlotIndex: Int
get() =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
inner.physicalSlotIndex
} else {
inner.slotIndex
}
override val ports: Collection<RealUiccPortInfoCompat>
get() =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
inner.ports.map { RealUiccPortInfoCompat(it, this) }
} else {
listOf(RealUiccPortInfoCompat(null, this))
}
val isEuicc: Boolean
get() = inner.isEuicc
val isRemovable: Boolean
get() = inner.isRemovable
val isMultipleEnabledProfilesSupported: Boolean
get() =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
inner.isMultipleEnabledProfilesSupported
} else {
false
}
val cardId: Int
get() = inner.cardId
}
class RealUiccPortInfoCompat(
private val _inner: Any?,
override val card: RealUiccCardInfoCompat
): UiccPortInfoCompat {
init {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
check(_inner != null && _inner is UiccPortInfo) {
"_inner is not UiccPortInfo on TIRAMISU"
}
}
}
private val inner: UiccPortInfo
get() =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
_inner as UiccPortInfo
} else {
throw RuntimeException("UiccPortInfo does not exist before T")
}
override val portIndex: Int
get() =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
inner.portIndex
} else {
0
}
override val logicalSlotIndex: Int
get() =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
inner.logicalSlotIndex
} else {
card.physicalSlotIndex // logical is the same as physical below TIRAMISU
}
}
val TelephonyManager.uiccCardsInfoCompat: List<RealUiccCardInfoCompat>
@SuppressLint("MissingPermission")
get() = uiccCardsInfo.map { RealUiccCardInfoCompat(it) }
// TODO: Usage of new APIs from T or later will still break build in-tree on lower AOSP versions
// Maybe older versions should simply include hidden-apis-shim when building?

View file

@ -3,6 +3,7 @@ package im.angry.openeuicc.util
import android.telephony.SubscriptionManager
import android.telephony.TelephonyManager
import android.telephony.UiccSlotMapping
import im.angry.openeuicc.core.EuiccChannel
import im.angry.openeuicc.core.EuiccChannelManager
import kotlinx.coroutines.runBlocking
import java.lang.Exception
@ -62,4 +63,14 @@ fun SubscriptionManager.tryRefreshCachedEuiccInfo(cardId: Int) {
// Ignore
}
}
}
}
// Every EuiccChannel we use here should be backed by a RealUiccPortInfoCompat
val EuiccChannel.removable
get() = (port as RealUiccPortInfoCompat).card.isRemovable
val EuiccChannel.cardId
get() = (port as RealUiccPortInfoCompat).card.cardId
val EuiccChannel.isMEP
get() = (port as RealUiccPortInfoCompat).card.isMultipleEnabledProfilesSupported

View file

@ -18,3 +18,4 @@ include ':libs:hidden-apis-stub'
include ':libs:hidden-apis-shim'
include ':libs:lpac-jni'
include ':app-common'
include ':app-unpriv'