Introducing EasyEUICC, the unprivileged version of OpenEUICC
|
@ -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>
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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>
|
|
@ -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
|
@ -0,0 +1 @@
|
|||
/build
|
62
app-unpriv/build.gradle
Normal 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
|
@ -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
|
25
app-unpriv/src/main/AndroidManifest.xml
Normal 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>
|
170
app-unpriv/src/main/res/drawable/ic_launcher_background.xml
Normal 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>
|
15
app-unpriv/src/main/res/drawable/ic_launcher_foreground.xml
Normal 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>
|
|
@ -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>
|
|
@ -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>
|
BIN
app-unpriv/src/main/res/mipmap-hdpi/ic_launcher.webp
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
app-unpriv/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
app-unpriv/src/main/res/mipmap-mdpi/ic_launcher.webp
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
app-unpriv/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
app-unpriv/src/main/res/mipmap-xhdpi/ic_launcher.webp
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
app-unpriv/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
app-unpriv/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
app-unpriv/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Normal file
After Width: | Height: | Size: 6.9 KiB |
BIN
app-unpriv/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Normal file
After Width: | Height: | Size: 4 KiB |
BIN
app-unpriv/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Normal file
After Width: | Height: | Size: 9.4 KiB |
8
app-unpriv/src/main/res/values/colors.xml
Normal 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>
|
3
app-unpriv/src/main/res/values/strings.xml
Normal file
|
@ -0,0 +1,3 @@
|
|||
<resources>
|
||||
<string name="app_name" translatable="false">EasyEUICC</string>
|
||||
</resources>
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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
|
|
@ -18,3 +18,4 @@ include ':libs:hidden-apis-stub'
|
|||
include ':libs:hidden-apis-shim'
|
||||
include ':libs:lpac-jni'
|
||||
include ':app-common'
|
||||
include ':app-unpriv'
|
||||
|
|