diff --git a/.forgejo/workflows/build-debug.yml b/.forgejo/workflows/build-debug.yml
index 0818b8b..51e802c 100644
--- a/.forgejo/workflows/build-debug.yml
+++ b/.forgejo/workflows/build-debug.yml
@@ -1,7 +1,7 @@
on:
push:
branches:
- - '*'
+ - 'master'
jobs:
build-debug:
@@ -33,23 +33,14 @@ jobs:
uses: https://gitea.angry.im/actions/setup-android@v3
- name: Build Debug APKs
- run: ./gradlew --no-daemon assembleDebug :app:assembleDebugMagiskModuleDir
+ run: ./gradlew --no-daemon assembleDebug
- name: Copy Artifacts
- run: |
- find . -name 'app*-debug.apk' -exec cp {} . \;
- cp -r app/build/magisk/debug ./magisk-debug
+ run: find . -name 'app*-debug.apk' -exec cp {} . \;
- - name: Upload APK Artifacts
+ - name: Upload Artifacts
uses: https://gitea.angry.im/actions/upload-artifact@v3
with:
name: Debug APKs
compression-level: 0
path: app*-debug.apk
-
- - name: Upload Magisk Artifacts
- uses: https://gitea.angry.im/actions/upload-artifact@v3
- with:
- name: magisk-debug
- compression-level: 0
- path: magisk-debug
diff --git a/.gitmodules b/.gitmodules
index 863f185..f888959 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,3 @@
[submodule "libs/lpac-jni/src/main/jni/lpac"]
path = libs/lpac-jni/src/main/jni/lpac
- url = https://github.com/estkme-group/lpac.git
+ url = https://github.com/estkme/lpac
diff --git a/.idea/.gitignore b/.idea/.gitignore
index d2293f6..b7c2402 100644
--- a/.idea/.gitignore
+++ b/.idea/.gitignore
@@ -1,7 +1,14 @@
-*
-!/codeStyles/Project.xml
-!/codeStyles/codeStyleConfig.xml
-!/vcs.xml
-!/kotlinc.xml
-!/compiler.xml
-!/migrations.xml
+/shelf
+/caches
+/libraries
+/assetWizardSettings.xml
+/deploymentTargetDropDown.xml
+/gradle.xml
+/misc.xml
+/modules.xml
+/navEditor.xml
+/runConfigurations.xml
+/workspace.xml
+/AndroidProjectSystem.xml
+
+**/*.iml
\ No newline at end of file
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index 7643783..4bec4ea 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -1,8 +1,5 @@
-
-
-
@@ -116,8 +113,5 @@
-
-
-
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
index 6e6eec1..a55e7a1 100644
--- a/.idea/codeStyles/codeStyleConfig.xml
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -1,6 +1,5 @@
-
\ No newline at end of file
diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
new file mode 100644
index 0000000..8096d6c
--- /dev/null
+++ b/.idea/deploymentTargetSelector.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
index 148fdd2..e805548 100644
--- a/.idea/kotlinc.xml
+++ b/.idea/kotlinc.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 80ab300..f8019b2 100644
--- a/README.md
+++ b/README.md
@@ -2,46 +2,25 @@
A fully free and open-source Local Profile Assistant implementation for Android devices.
-There are two variants of this project, OpenEUICC and EasyEUICC:
+There are two variants of this project:
-| | OpenEUICC | EasyEUICC |
-| :---------------------------- | :-----------------------------: | :-----------------: |
-| Privileged | Must be installed as system app | No |
-| Internal eSIM | Supported | Unsupported |
-| External eSIM [^1] | Supported | Supported |
-| USB Readers | Supported | Supported |
-| Requires allowlisting by eSIM | No | Yes -- except USB |
-| System Integration | Partial [^2] | No |
-| Minimum Android Version | Android 11 or higher | Android 9 or higher |
+- OpenEUICC: The full-fledged privileged variant.
+ - Due to its privilege requirement, OpenEUICC must be placed inside `/system/priv-app` and be signed with the platform certificate.
+ - The preferred way to including OpenEUICC in a system image is to [build it along with AOSP](#building-aosp).
+ - __Note__: When privileged, OpenEUICC supports any eUICC chip that implements the SGP.22 standard, internal or external. However, there is __no guarantee__ that external (removable) eSIMs actually follow the standard. Please __DO NOT__ submit bug reports for non-functioning removable eSIMs. They are __NOT__ officially supported unless they also support / are supported by EasyEUICC, the unprivileged variant.
+- EasyEUICC: Unprivileged version that can run as a user app.
+ - This version supports two modes of operation:
+ 1. Inserted, removable eSIMs: Due to obvious security requirements, EasyEUICC is only able to access eSIM chips whose [ARF/ARA](https://source.android.com/docs/core/connect/uicc#arf) contains the hash of EasyEUICC's signing certificate.
+ 2. USB CCID Card Readers: Only `T=0` readers that use the standard [USB CCID protocol](https://en.wikipedia.org/wiki/CCID_(protocol)) are supported. In this mode, EasyEUICC can access any eSIM chip loaded in the card reader regardless of their ARF/ARA, as long as they implement the [SGP.22 standard](https://www.gsma.com/solutions-and-impact/technologies/esim/wp-content/uploads/2021/07/SGP.22-v2.3.pdf).
+ - Prebuilt release-mode EasyEUICC apks can be downloaded [here](https://gitea.angry.im/PeterCxy/OpenEUICC/releases)
+ - For removable eSIM chip vendors: to have your chip supported by official builds of EasyEUICC when inserted, include the ARA-M hash `2A2FA878BC7C3354C2CF82935A5945A3EDAE4AFA`
-[^1]: Also known as "Removable eSIM"
-[^2]: Carrier Partner API unimplemented yet
+__This project is Free Software licensed under GNU GPL v3, WITHOUT the "or later" clause.__ Any modification and derivative work __MUST__ be released under the SAME license, which means, at the very least, that the source code __MUST__ be available upon request.
-Some side notes:
+__If you are releasing a modification of this app, you are kindly asked to make changes to at least the app name and package name.__
-1. When privileged, OpenEUICC supports any eUICC chip that implements the [SGP.22] standard, internal or external.
- However, there is **no guarantee** that external (removable) eSIMs actually follow the standard.
- Please **DO NOT** submit bug reports for non-functioning removable eSIMs.
- They are **NOT** officially supported unless they also support / are supported by EasyEUICC, the unprivileged variant.
-2. Both variants support accessing eUICC chips through USB CCID readers,
- regardless of whether the chip contains the correct ARA-M hash to allow for unprivileged access.
- However, only `T=0` readers that use the standard [USB CCID protocol][usb-ccid] are supported.
-3. Prebuilt release-mode EasyEUICC apks can be downloaded [here][releases].
- For OpenEUICC, no official release is currently provided and only debug mode APKs and Magisk modules can be found in the [CI page][actions].
-4. For removable eSIM chip vendors: to have your chip supported by official builds of EasyEUICC when inserted,
- include the ARA-M hash `2A2FA878BC7C3354C2CF82935A5945A3EDAE4AFA`.
-
-[sgp.22]: https://www.gsma.com/solutions-and-impact/technologies/esim/gsma_resources/sgp-22-v2-2-2/ "SGP.22 v2.2.2"
-[usb-ccid]: https://en.wikipedia.org/wiki/CCID_%28protocol%29 "USB CCID Protocol"
-[releases]: https://gitea.angry.im/PeterCxy/OpenEUICC/releases "EasyEUICC Releases"
-[actions]: https://gitea.angry.im/PeterCxy/OpenEUICC/actions "OpenEUICC Actions"
-
-**This project is Free Software licensed under GNU GPL v3, WITHOUT the "or later" clause.**
-Any modification and derivative work **MUST** be released under the SAME license, which means, at the very least, that the source code **MUST** be available upon request.
-
-**If you are releasing a modification of this app, you are kindly asked to make changes to at least the app name and package name.**
-
-# Building (Gradle)
+Building (Gradle)
+===
Make sure you have all submodules cloned and updated by running
@@ -74,7 +53,8 @@ For EasyEUICC:
./gradlew :app-unpriv:assembleRelease
```
-# Building (AOSP)
+Building (AOSP)
+===
There are two ways to include OpenEUICC in your AOSP-based system image:
@@ -84,22 +64,25 @@ There are two ways to include OpenEUICC in your AOSP-based system image:
- Compilation of this project is **only** tested against the latest AOSP release version. The app itself should be compatible with older AOSP versions, but the source may not compile against an older AOSP source tree.
2. If compilation against AOSP source tree is not possible, consider [building with gradle](#building-gradle) and import the apk as a prebuilt.
- No official `Android.bp` is provided for this case but it should be straightforward to write.
- - You might want to include [`privapp_whitelist_im.angry.openeuicc.xml`] as well.
+ - You might want to include `privapp_whitelist_im.angry.openeuicc.xml` as well.
-[`privapp_whitelist_im.angry.openeuicc.xml`]: privapp_whitelist_im.angry.openeuicc.xml "OpenEUICC Privapp Whitelist"
+FAQs
+===
-# FAQs
+- Q: Do you provide prebuilt binaries for OpenEUICC?
+- A: Debug-mode APKs are available continuously as an artifact of the [Actions](https://gitea.angry.im/PeterCxy/OpenEUICC/actions) CI used by this project. However, these debug-mode APKs are **not** intended for inclusion inside system images, nor are they supported by the developer in any sense. If you are a custom ROM developer, either include the entire OpenEUICC repository in your AOSP source tree, or generate an APK using `gradle` and import that as a prebuilt system app. Note that you might want `privapp_whitelist_im.angry.openeuicc.xml` as well.
-- Q: Do you provide prebuilt binaries for OpenEUICC? \
- A: Debug-mode APKs and Magisk modules are available continuously as an artifact of the [Actions] CI used by this project. However, these debug-mode APKs are **not** intended for inclusion inside system images, nor are they supported by the developer in any sense. If you are a custom ROM developer, either include the entire OpenEUICC repository in your AOSP source tree, or generate an APK using `gradle` and import that as a prebuilt system app. Note that you might want [`privapp_whitelist_im.angry.openeuicc.xml`] as well.
+- Q: AOSP's Settings app seems to be confused by OpenEUICC (for example, disabling / enabling profiles from the Networks page do not work properly)
+- A: When your device has internal eSIM chip(s) __and__ you have inserted a removable eSIM chip, the Settings app can misbehave since it was never designed for this scenario. __Please prefer using OpenEUICC's own management interface whenever possible.__ In the future, there might be an option to exclude removable SIMs from being reported to the Android system.
-- Q: Can EasyEUICC manage my phone's internal eSIM? \
- A: No. For EasyEUICC to work, the eSIM chip MUST proactively grant access via its ARA-M field.
+- Q: Can EasyEUICC manage my phone's internal eSIM?
+- A: No. For EasyEUICC to work, the eSIM chip MUST proactively grant access via its ARA-M field.
-- Q: Removable eSIMs? Are they a joke? \
- A: No, even though the name "removable embedded SIM" can sound like an oxymoron. In fact, there can be many advantages to these chips compared to fully embedded ones. For example, the ability to transfer eSIM profiles without carrier support or approval, or the ability to use eSIM on devices that do not and may never get the support, such as Wi-Fi hotspots.
+- Q: Removable eSIMs? Are they a joke?
+- A: No, even though the name "removable embedded SIM" can sound like an oxymoron. In fact, there can be many advantages to these chips compared to fully embedded ones. For example, the ability to transfer eSIM profiles without carrier support or approval, or the ability to use eSIM on devices that do not and may never get the support, such as Wi-Fi hotspots.
-# Copyright
+Copyright
+===
Everything except `libs/lpac-jni` and `art/`:
@@ -139,4 +122,4 @@ License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
```
-`art/`: Courtesy of [Aikoyori](https://github.com/Aikoyori), CC NC-SA 4.0.
+`art/`: Courtesy of [Aikoyori](https://github.com/Aikoyori), CC NC-SA 4.0.
\ No newline at end of file
diff --git a/app-common/src/main/AndroidManifest.xml b/app-common/src/main/AndroidManifest.xml
index 44c82c0..6edaaf1 100644
--- a/app-common/src/main/AndroidManifest.xml
+++ b/app-common/src/main/AndroidManifest.xml
@@ -7,7 +7,6 @@
-
-
-
-
-
-
+
+
diff --git a/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelFactory.kt b/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelFactory.kt
index 78a8c3f..ea0bd60 100644
--- a/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelFactory.kt
+++ b/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelFactory.kt
@@ -1,86 +1,84 @@
package im.angry.openeuicc.core
import android.content.Context
+import android.hardware.usb.UsbDevice
+import android.hardware.usb.UsbInterface
+import android.hardware.usb.UsbManager
import android.se.omapi.SEService
import android.util.Log
import im.angry.openeuicc.common.R
import im.angry.openeuicc.core.usb.UsbApduInterface
-import im.angry.openeuicc.core.usb.UsbCcidContext
+import im.angry.openeuicc.core.usb.bulkPair
+import im.angry.openeuicc.core.usb.endpoints
import im.angry.openeuicc.util.*
import java.lang.IllegalArgumentException
open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccChannelFactory {
private var seService: SEService? = null
+ private val usbManager by lazy {
+ context.getSystemService(Context.USB_SERVICE) as UsbManager
+ }
+
private suspend fun ensureSEService() {
if (seService == null || !seService!!.isConnected) {
seService = connectSEService(context)
}
}
- override suspend fun tryOpenEuiccChannel(
- port: UiccPortInfoCompat,
- isdrAid: ByteArray
- ): EuiccChannel? = try {
+ override suspend fun tryOpenEuiccChannel(port: UiccPortInfoCompat): EuiccChannel? {
if (port.portIndex != 0) {
- Log.w(
- DefaultEuiccChannelManager.TAG,
- "OMAPI channel attempted on non-zero portId, this may or may not work."
- )
+ Log.w(DefaultEuiccChannelManager.TAG, "OMAPI channel attempted on non-zero portId, this may or may not work.")
}
ensureSEService()
- Log.i(
- DefaultEuiccChannelManager.TAG,
- "Trying OMAPI for physical slot ${port.card.physicalSlotIndex}"
- )
- EuiccChannelImpl(
- context.getString(R.string.channel_type_omapi),
- port,
- intrinsicChannelName = null,
- OmapiApduInterface(
- seService!!,
+ Log.i(DefaultEuiccChannelManager.TAG, "Trying OMAPI for physical slot ${port.card.physicalSlotIndex}")
+ try {
+ return EuiccChannelImpl(
+ context.getString(R.string.omapi),
port,
- context.preferenceRepository.verboseLoggingFlow
- ),
- isdrAid,
- context.preferenceRepository.verboseLoggingFlow,
- context.preferenceRepository.ignoreTLSCertificateFlow,
- context.preferenceRepository.es10xMssFlow,
- )
- } catch (_: IllegalArgumentException) {
- // Failed
- Log.w(
- DefaultEuiccChannelManager.TAG,
- "OMAPI APDU interface unavailable for physical slot ${port.card.physicalSlotIndex} with ISD-R AID: ${isdrAid.encodeHex()}."
- )
- null
+ intrinsicChannelName = null,
+ OmapiApduInterface(
+ seService!!,
+ port,
+ context.preferenceRepository.verboseLoggingFlow
+ ),
+ context.preferenceRepository.verboseLoggingFlow,
+ context.preferenceRepository.ignoreTLSCertificateFlow,
+ ).also {
+ Log.i(DefaultEuiccChannelManager.TAG, "Is OMAPI channel, setting MSS to 60")
+ it.lpa.setEs10xMss(60)
+ }
+ } catch (e: IllegalArgumentException) {
+ // Failed
+ Log.w(
+ DefaultEuiccChannelManager.TAG,
+ "OMAPI APDU interface unavailable for physical slot ${port.card.physicalSlotIndex}."
+ )
+ }
+
+ return null
}
- override fun tryOpenUsbEuiccChannel(
- ccidCtx: UsbCcidContext,
- isdrAid: ByteArray
- ): EuiccChannel? = try {
- EuiccChannelImpl(
- context.getString(R.string.channel_type_usb),
+ override fun tryOpenUsbEuiccChannel(usbDevice: UsbDevice, usbInterface: UsbInterface): EuiccChannel? {
+ val (bulkIn, bulkOut) = usbInterface.endpoints.bulkPair
+ if (bulkIn == null || bulkOut == null) return null
+ val conn = usbManager.openDevice(usbDevice) ?: return null
+ if (!conn.claimInterface(usbInterface, true)) return null
+ return EuiccChannelImpl(
+ context.getString(R.string.usb),
FakeUiccPortInfoCompat(FakeUiccCardInfoCompat(EuiccChannelManager.USB_CHANNEL_ID)),
- intrinsicChannelName = ccidCtx.productName,
+ intrinsicChannelName = usbDevice.productName,
UsbApduInterface(
- ccidCtx
+ conn,
+ bulkIn,
+ bulkOut,
+ context.preferenceRepository.verboseLoggingFlow
),
- isdrAid,
context.preferenceRepository.verboseLoggingFlow,
context.preferenceRepository.ignoreTLSCertificateFlow,
- context.preferenceRepository.es10xMssFlow,
)
- } catch (_: IllegalArgumentException) {
- // Failed
- Log.w(
- DefaultEuiccChannelManager.TAG,
- "USB APDU interface unavailable for ISD-R AID: ${isdrAid.encodeHex()}."
- )
- null
}
override fun cleanup() {
diff --git a/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelManager.kt b/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelManager.kt
index 6b336cd..dd57eab 100644
--- a/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelManager.kt
+++ b/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelManager.kt
@@ -5,7 +5,6 @@ import android.hardware.usb.UsbDevice
import android.hardware.usb.UsbManager
import android.telephony.SubscriptionManager
import android.util.Log
-import im.angry.openeuicc.core.usb.UsbCcidContext
import im.angry.openeuicc.core.usb.smartCard
import im.angry.openeuicc.core.usb.interfaces
import im.angry.openeuicc.di.AppContainer
@@ -13,7 +12,6 @@ import im.angry.openeuicc.util.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.merge
@@ -51,24 +49,6 @@ open class DefaultEuiccChannelManager(
protected open val uiccCards: Collection
get() = (0.. EuiccChannel?): EuiccChannel? {
- val isdrAidList =
- parseIsdrAidList(appContainer.preferenceRepository.isdrAidListFlow.first())
-
- return isdrAidList.firstNotNullOfOrNull {
- Log.i(TAG, "Opening channel, trying ISDR AID ${it.encodeHex()}")
-
- openFn(it)?.let { channel ->
- if (channel.valid) {
- channel
- } else {
- channel.close()
- null
- }
- }
- }
- }
-
private suspend fun tryOpenEuiccChannel(port: UiccPortInfoCompat): EuiccChannel? {
lock.withLock {
if (port.card.physicalSlotIndex == EuiccChannelManager.USB_CHANNEL_ID) {
@@ -96,10 +76,9 @@ open class DefaultEuiccChannelManager(
return null
}
- val channel =
- tryOpenChannelFirstValidAid { euiccChannelFactory.tryOpenEuiccChannel(port, it) }
+ val channel = euiccChannelFactory.tryOpenEuiccChannel(port) ?: return null
- if (channel != null) {
+ if (channel.valid) {
channelCache.add(channel)
return channel
} else {
@@ -107,6 +86,7 @@ open class DefaultEuiccChannelManager(
TAG,
"Was able to open channel for logical slot ${port.logicalSlotIndex}, but the channel is invalid (cannot get eID or profiles without errors). This slot might be broken, aborting."
)
+ channel.close()
return null
}
}
@@ -232,10 +212,7 @@ open class DefaultEuiccChannelManager(
check(channel.valid) { "Invalid channel" }
break
} catch (e: Exception) {
- Log.d(
- TAG,
- "Slot $physicalSlotId port $portId reconnect failure, retrying in 1000 ms"
- )
+ Log.d(TAG, "Slot $physicalSlotId port $portId reconnect failure, retrying in 1000 ms")
}
delay(1000)
}
@@ -272,19 +249,10 @@ open class DefaultEuiccChannelManager(
// If we don't have permission, tell UI code that we found a candidate device, but we
// need permission to be able to do anything with it
if (!usbManager.hasPermission(device)) return@withContext Pair(device, false)
- Log.i(
- TAG,
- "Found CCID interface on ${device.deviceId}:${device.vendorId}, and has permission; trying to open channel"
- )
-
- val ccidCtx = UsbCcidContext.createFromUsbDevice(context, device, iface) ?: return@forEach
-
+ Log.i(TAG, "Found CCID interface on ${device.deviceId}:${device.vendorId}, and has permission; trying to open channel")
try {
- val channel = tryOpenChannelFirstValidAid {
- euiccChannelFactory.tryOpenUsbEuiccChannel(ccidCtx, it)
- }
+ val channel = euiccChannelFactory.tryOpenUsbEuiccChannel(device, iface)
if (channel != null && channel.lpa.valid) {
- ccidCtx.allowDisconnect = true
usbChannel = channel
return@withContext Pair(device, true)
}
@@ -292,14 +260,7 @@ open class DefaultEuiccChannelManager(
// Ignored -- skip forward
e.printStackTrace()
}
-
- ccidCtx.allowDisconnect = true
- ccidCtx.disconnect()
-
- Log.i(
- TAG,
- "No valid eUICC channel found on USB device ${device.deviceId}:${device.vendorId}"
- )
+ Log.i(TAG, "No valid eUICC channel found on USB device ${device.deviceId}:${device.vendorId}")
}
return@withContext Pair(null, false)
}
diff --git a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannel.kt b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannel.kt
index b20932f..597a70d 100644
--- a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannel.kt
+++ b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannel.kt
@@ -34,10 +34,5 @@ interface EuiccChannel {
*/
val apduInterface: ApduInterface
- /**
- * The AID of the ISD-R channel currently in use
- */
- val isdrAid: ByteArray
-
fun close()
}
\ No newline at end of file
diff --git a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelFactory.kt b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelFactory.kt
index ba587a6..fb5d95d 100644
--- a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelFactory.kt
+++ b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelFactory.kt
@@ -1,17 +1,15 @@
package im.angry.openeuicc.core
-import im.angry.openeuicc.core.usb.UsbCcidContext
+import android.hardware.usb.UsbDevice
+import android.hardware.usb.UsbInterface
import im.angry.openeuicc.util.*
// This class is here instead of inside DI because it contains a bit more logic than just
// "dumb" dependency injection.
interface EuiccChannelFactory {
- suspend fun tryOpenEuiccChannel(port: UiccPortInfoCompat, isdrAid: ByteArray): EuiccChannel?
+ suspend fun tryOpenEuiccChannel(port: UiccPortInfoCompat): EuiccChannel?
- fun tryOpenUsbEuiccChannel(
- ccidCtx: UsbCcidContext,
- isdrAid: ByteArray
- ): EuiccChannel?
+ fun tryOpenUsbEuiccChannel(usbDevice: UsbDevice, usbInterface: UsbInterface): EuiccChannel?
/**
* Release all resources used by this EuiccChannelFactory
diff --git a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelImpl.kt b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelImpl.kt
index eaec522..a56b1cc 100644
--- a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelImpl.kt
+++ b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelImpl.kt
@@ -1,9 +1,8 @@
package im.angry.openeuicc.core
import im.angry.openeuicc.util.UiccPortInfoCompat
+import im.angry.openeuicc.util.decodeHex
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.runBlocking
import net.typeblog.lpac_jni.ApduInterface
import net.typeblog.lpac_jni.LocalProfileAssistant
import net.typeblog.lpac_jni.impl.HttpInterfaceImpl
@@ -14,23 +13,24 @@ class EuiccChannelImpl(
override val port: UiccPortInfoCompat,
override val intrinsicChannelName: String?,
override val apduInterface: ApduInterface,
- override val isdrAid: ByteArray,
verboseLoggingFlow: Flow,
- ignoreTLSCertificateFlow: Flow,
- es10xMssFlow: Flow,
+ ignoreTLSCertificateFlow: Flow
) : EuiccChannel {
+ companion object {
+ // TODO: This needs to go somewhere else.
+ val ISDR_AID = "A0000005591010FFFFFFFF8900000100".decodeHex()
+ }
+
override val slotId = port.card.physicalSlotIndex
override val logicalSlotId = port.logicalSlotIndex
override val portId = port.portIndex
override val lpa: LocalProfileAssistant =
LocalProfileAssistantImpl(
- isdrAid,
+ ISDR_AID,
apduInterface,
- HttpInterfaceImpl(verboseLoggingFlow, ignoreTLSCertificateFlow),
- ).also {
- it.setEs10xMss(runBlocking { es10xMssFlow.first().toByte() })
- }
+ HttpInterfaceImpl(verboseLoggingFlow, ignoreTLSCertificateFlow)
+ )
override val atr: ByteArray?
get() = (apduInterface as? ApduInterfaceAtrProvider)?.atr
diff --git a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelWrapper.kt b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelWrapper.kt
index 361a943..09004d3 100644
--- a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelWrapper.kt
+++ b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelWrapper.kt
@@ -38,8 +38,6 @@ class EuiccChannelWrapper(orig: EuiccChannel) : EuiccChannel {
get() = channel.apduInterface
override val atr: ByteArray?
get() = channel.atr
- override val isdrAid: ByteArray
- get() = channel.isdrAid
override fun close() = channel.close()
diff --git a/app-common/src/main/java/im/angry/openeuicc/core/usb/UsbApduInterface.kt b/app-common/src/main/java/im/angry/openeuicc/core/usb/UsbApduInterface.kt
index 4a4ccb9..f9e764b 100644
--- a/app-common/src/main/java/im/angry/openeuicc/core/usb/UsbApduInterface.kt
+++ b/app-common/src/main/java/im/angry/openeuicc/core/usb/UsbApduInterface.kt
@@ -1,19 +1,27 @@
package im.angry.openeuicc.core.usb
+import android.hardware.usb.UsbDeviceConnection
+import android.hardware.usb.UsbEndpoint
import android.util.Log
import im.angry.openeuicc.core.ApduInterfaceAtrProvider
import im.angry.openeuicc.util.*
+import kotlinx.coroutines.flow.Flow
import net.typeblog.lpac_jni.ApduInterface
class UsbApduInterface(
- private val ccidCtx: UsbCcidContext
+ private val conn: UsbDeviceConnection,
+ private val bulkIn: UsbEndpoint,
+ private val bulkOut: UsbEndpoint,
+ private val verboseLoggingFlow: Flow
) : ApduInterface, ApduInterfaceAtrProvider {
companion object {
private const val TAG = "UsbApduInterface"
}
- override val atr: ByteArray?
- get() = ccidCtx.atr
+ private lateinit var ccidDescription: UsbCcidDescription
+ private lateinit var transceiver: UsbCcidTransceiver
+
+ override var atr: ByteArray? = null
override val valid: Boolean
get() = channels.isNotEmpty()
@@ -21,19 +29,29 @@ class UsbApduInterface(
private var channels = mutableSetOf()
override fun connect() {
- ccidCtx.connect()
+ ccidDescription = UsbCcidDescription.fromRawDescriptors(conn.rawDescriptors)!!
- // Send Terminal Capabilities
- // Specs: ETSI TS 102 221 v15.0.0 - 11.1.19 TERMINAL CAPABILITY
- val terminalCapabilities = buildCmd(
- 0x80.toByte(), 0xaa.toByte(), 0x00, 0x00,
- "A9088100820101830107".decodeHex(),
- le = null,
- )
- transmitApduByChannel(terminalCapabilities, 0)
+ if (!ccidDescription.hasT0Protocol) {
+ throw IllegalArgumentException("Unsupported card reader; T=0 support is required")
+ }
+
+ transceiver = UsbCcidTransceiver(conn, bulkIn, bulkOut, ccidDescription, verboseLoggingFlow)
+
+ try {
+ // 6.1.1.1 PC_to_RDR_IccPowerOn (Page 20 of 40)
+ // https://www.usb.org/sites/default/files/DWG_Smart-Card_USB-ICC_ICCD_rev10.pdf
+ atr = transceiver.iccPowerOn().data
+ } catch (e: Exception) {
+ e.printStackTrace()
+ throw e
+ }
}
- override fun disconnect() = ccidCtx.disconnect()
+ override fun disconnect() {
+ conn.close()
+
+ atr = null
+ }
override fun logicalChannelOpen(aid: ByteArray): Int {
// OPEN LOGICAL CHANNEL
@@ -122,7 +140,7 @@ class UsbApduInterface(
// OR the channel mask into the CLA byte
realTx[0] = ((realTx[0].toInt() and 0xFC) or channel.toInt()).toByte()
- var resp = ccidCtx.transceiver.sendXfrBlock(realTx).data!!
+ var resp = transceiver.sendXfrBlock(realTx).data!!
if (resp.size < 2) throw RuntimeException("APDU response smaller than 2 (sw1 + sw2)!")
@@ -133,7 +151,7 @@ class UsbApduInterface(
// 0x6C = wrong le
// so we fix the le field here
realTx[realTx.size - 1] = resp[resp.size - 1]
- resp = ccidCtx.transceiver.sendXfrBlock(realTx).data!!
+ resp = transceiver.sendXfrBlock(realTx).data!!
} else if (sw1 == 0x61) {
// 0x61 = X bytes available
// continue reading by GET RESPONSE
@@ -143,7 +161,7 @@ class UsbApduInterface(
realTx[0], 0xC0.toByte(), 0x00, 0x00, sw2.toByte()
)
- val tmp = ccidCtx.transceiver.sendXfrBlock(getResponseCmd).data!!
+ val tmp = transceiver.sendXfrBlock(getResponseCmd).data!!
resp = resp.sliceArray(0 until (resp.size - 2)) + tmp
diff --git a/app-common/src/main/java/im/angry/openeuicc/core/usb/UsbCcidContext.kt b/app-common/src/main/java/im/angry/openeuicc/core/usb/UsbCcidContext.kt
deleted file mode 100644
index caf69e7..0000000
--- a/app-common/src/main/java/im/angry/openeuicc/core/usb/UsbCcidContext.kt
+++ /dev/null
@@ -1,87 +0,0 @@
-package im.angry.openeuicc.core.usb
-
-import android.content.Context
-import android.hardware.usb.UsbDevice
-import android.hardware.usb.UsbDeviceConnection
-import android.hardware.usb.UsbEndpoint
-import android.hardware.usb.UsbInterface
-import android.hardware.usb.UsbManager
-import im.angry.openeuicc.util.preferenceRepository
-import kotlinx.coroutines.flow.Flow
-
-/**
- * A wrapper over an usb device + interface, manages the lifecycle independent
- * of the APDU interface exposed to lpac-jni.
- *
- * This allows us to try multiple AIDs on each interface without opening / closing
- * the USB connection numerous times.
- */
-class UsbCcidContext private constructor(
- private val conn: UsbDeviceConnection,
- private val bulkIn: UsbEndpoint,
- private val bulkOut: UsbEndpoint,
- val productName: String,
- val verboseLoggingFlow: Flow
-) {
- companion object {
- fun createFromUsbDevice(
- context: Context,
- usbDevice: UsbDevice,
- usbInterface: UsbInterface
- ): UsbCcidContext? = runCatching {
- val (bulkIn, bulkOut) = usbInterface.endpoints.bulkPair
- if (bulkIn == null || bulkOut == null) return@runCatching null
- val conn = context.getSystemService(UsbManager::class.java).openDevice(usbDevice)
- ?: return@runCatching null
- if (!conn.claimInterface(usbInterface, true)) return@runCatching null
- UsbCcidContext(
- conn,
- bulkIn,
- bulkOut,
- usbDevice.productName ?: "USB",
- context.preferenceRepository.verboseLoggingFlow
- )
- }.getOrNull()
- }
-
- /**
- * When set to false (the default), the disconnect() method does nothing.
- * This allows the separation of device disconnection from lpac-jni's APDU interface.
- */
- var allowDisconnect = false
- private var initialized = false
- lateinit var transceiver: UsbCcidTransceiver
- var atr: ByteArray? = null
-
- fun connect() {
- if (initialized) {
- return
- }
-
- val ccidDescription = UsbCcidDescription.fromRawDescriptors(conn.rawDescriptors)!!
-
- if (!ccidDescription.hasT0Protocol) {
- throw IllegalArgumentException("Unsupported card reader; T=0 support is required")
- }
-
- transceiver = UsbCcidTransceiver(conn, bulkIn, bulkOut, ccidDescription, verboseLoggingFlow)
-
- try {
- // 6.1.1.1 PC_to_RDR_IccPowerOn (Page 20 of 40)
- // https://www.usb.org/sites/default/files/DWG_Smart-Card_USB-ICC_ICCD_rev10.pdf
- atr = transceiver.iccPowerOn().data
- } catch (e: Exception) {
- e.printStackTrace()
- throw e
- }
-
- initialized = true
- }
-
- fun disconnect() {
- if (initialized && allowDisconnect) {
- conn.close()
- atr = null
- }
- }
-}
diff --git a/app-common/src/main/java/im/angry/openeuicc/di/DefaultCustomizableTextProvider.kt b/app-common/src/main/java/im/angry/openeuicc/di/DefaultCustomizableTextProvider.kt
index 76227fd..b493611 100644
--- a/app-common/src/main/java/im/angry/openeuicc/di/DefaultCustomizableTextProvider.kt
+++ b/app-common/src/main/java/im/angry/openeuicc/di/DefaultCustomizableTextProvider.kt
@@ -8,7 +8,7 @@ open class DefaultCustomizableTextProvider(private val context: Context) : Custo
get() = context.getString(R.string.no_euicc)
override val profileSwitchingTimeoutMessage: String
- get() = context.getString(R.string.profile_switch_timeout)
+ get() = context.getString(R.string.enable_disable_timeout)
override fun formatInternalChannelName(logicalSlotId: Int): String =
context.getString(R.string.channel_name_format, logicalSlotId)
diff --git a/app-common/src/main/java/im/angry/openeuicc/service/EuiccChannelManagerService.kt b/app-common/src/main/java/im/angry/openeuicc/service/EuiccChannelManagerService.kt
index 4744321..9957f30 100644
--- a/app-common/src/main/java/im/angry/openeuicc/service/EuiccChannelManagerService.kt
+++ b/app-common/src/main/java/im/angry/openeuicc/service/EuiccChannelManagerService.kt
@@ -4,7 +4,6 @@ import android.content.Intent
import android.content.pm.PackageManager
import android.os.Binder
import android.os.IBinder
-import android.os.PowerManager
import android.util.Log
import androidx.core.app.NotificationChannelCompat
import androidx.core.app.NotificationCompat
@@ -92,12 +91,6 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker {
}
val euiccChannelManager: EuiccChannelManager by euiccChannelManagerDelegate
- private val wakeLock: PowerManager.WakeLock by lazy {
- (getSystemService(POWER_SERVICE) as PowerManager).run {
- newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this::class.simpleName)
- }
- }
-
/**
* The state of a "foreground" task (named so due to the need to startForeground())
*/
@@ -282,8 +275,6 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker {
updateForegroundNotification(title, iconRes)
- wakeLock.acquire(10 * 60 * 1000L /*10 minutes*/)
-
try {
withContext(Dispatchers.IO + NonCancellable) { // Any LPA-related task must always complete
this@EuiccChannelManagerService.task()
@@ -299,7 +290,6 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker {
postForegroundTaskFailureNotification(failureTitle)
}
} finally {
- wakeLock.release()
if (isActive) {
stopSelf()
}
@@ -456,34 +446,30 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker {
iccid: String,
enable: Boolean, // Enable or disable the profile indicated in iccid
reconnectTimeoutMillis: Long = 0 // 0 = do not wait for reconnect
- ) =
+ ): ForegroundTaskSubscriberFlow =
launchForegroundTask(
getString(R.string.task_profile_switch),
getString(R.string.task_profile_switch_failure),
R.drawable.ic_task_switch
) {
euiccChannelManager.beginTrackedOperation(slotId, portId) {
- val (response, refreshed) =
- euiccChannelManager.withEuiccChannel(slotId, portId) { channel ->
- val refresh = preferenceRepository.refreshAfterSwitchFlow.first()
- val response = channel.lpa.switchProfile(iccid, enable, refresh)
- if (response || !refresh) {
- Pair(response, refresh)
- } else {
- // refresh failed, but refresh was requested
- // Sometimes, we *can* enable or disable the profile, but we cannot
- // send the refresh command to the modem because the profile somehow
- // makes the modem "busy". In this case, we can still switch by setting
- // refresh to false, but then the switch cannot take effect until the
- // user resets the modem manually by toggling airplane mode or rebooting.
- Pair(
- channel.lpa.switchProfile(iccid, enable, refresh = false),
- false
- )
- }
+ val (res, refreshed) = euiccChannelManager.withEuiccChannel(
+ slotId,
+ portId
+ ) { channel ->
+ if (!channel.lpa.switchProfile(iccid, enable, refresh = true)) {
+ // Sometimes, we *can* enable or disable the profile, but we cannot
+ // send the refresh command to the modem because the profile somehow
+ // makes the modem "busy". In this case, we can still switch by setting
+ // refresh to false, but then the switch cannot take effect until the
+ // user resets the modem manually by toggling airplane mode or rebooting.
+ Pair(channel.lpa.switchProfile(iccid, enable, refresh = false), false)
+ } else {
+ Pair(true, true)
}
+ }
- if (!response) {
+ if (!res) {
throw RuntimeException("Could not switch profile")
}
@@ -516,12 +502,8 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker {
getString(R.string.task_euicc_memory_reset_failure),
R.drawable.ic_euicc_memory_reset
) {
- euiccChannelManager.beginTrackedOperation(slotId, portId) {
- euiccChannelManager.withEuiccChannel(slotId, portId) { channel ->
- channel.lpa.euiccMemoryReset()
- }
-
- preferenceRepository.notificationDeleteFlow.first()
+ euiccChannelManager.withEuiccChannel(slotId, portId) { channel ->
+ channel.lpa.euiccMemoryReset()
}
}
}
\ No newline at end of file
diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/EuiccInfoActivity.kt b/app-common/src/main/java/im/angry/openeuicc/ui/EuiccInfoActivity.kt
index 248afaf..4e499dc 100644
--- a/app-common/src/main/java/im/angry/openeuicc/ui/EuiccInfoActivity.kt
+++ b/app-common/src/main/java/im/angry/openeuicc/ui/EuiccInfoActivity.kt
@@ -27,16 +27,9 @@ import kotlinx.coroutines.launch
import net.typeblog.lpac_jni.impl.PKID_GSMA_LIVE_CI
import net.typeblog.lpac_jni.impl.PKID_GSMA_TEST_CI
-// https://euicc-manual.osmocom.org/docs/pki/eum/accredited.json
-// ref:
-private val RE_SAS = Regex(
- """^[A-Z]{2}-[A-Z]{2}(?:-UP)?-\d{4}T?(?:-\d+)?T?$""",
- setOf(RegexOption.IGNORE_CASE),
-)
-
class EuiccInfoActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
companion object {
- private val YES_NO = Pair(R.string.euicc_info_yes, R.string.euicc_info_no)
+ private val YES_NO = Pair(R.string.yes, R.string.no)
}
private lateinit var swipeRefresh: SwipeRefreshLayout
@@ -69,7 +62,7 @@ class EuiccInfoActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
logicalSlotId = intent.getIntExtra("logicalSlotId", 0)
val channelTitle = if (logicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) {
- getString(R.string.channel_type_usb)
+ getString(R.string.usb)
} else {
appContainer.customizableTextProvider.formatInternalChannelName(logicalSlotId)
}
@@ -109,27 +102,19 @@ class EuiccInfoActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
add(Item(R.string.euicc_info_access_mode, channel.type))
add(Item(R.string.euicc_info_removable, formatByBoolean(channel.port.card.isRemovable, YES_NO)))
add(Item(R.string.euicc_info_eid, channel.lpa.eID, copiedToastResId = R.string.toast_eid_copied))
- add(Item(R.string.euicc_info_isdr_aid, channel.isdrAid.encodeHex()))
channel.tryParseEuiccVendorInfo()?.let { vendorInfo ->
vendorInfo.skuName?.let { add(Item(R.string.euicc_info_sku, it)) }
- vendorInfo.serialNumber?.let { add(Item(R.string.euicc_info_sn, it, copiedToastResId = R.string.toast_sn_copied)) }
+ vendorInfo.serialNumber?.let { add(Item(R.string.euicc_info_sn, it)) }
vendorInfo.firmwareVersion?.let { add(Item(R.string.euicc_info_fw_ver, it)) }
vendorInfo.bootloaderVersion?.let { add(Item(R.string.euicc_info_bl_ver, it)) }
}
- channel.lpa.euiccInfo2?.let { info ->
- add(Item(R.string.euicc_info_sgp22_version, info.sgp22Version.toString()))
- add(Item(R.string.euicc_info_firmware_version, info.euiccFirmwareVersion.toString()))
- add(Item(R.string.euicc_info_gp_version, info.globalPlatformVersion.toString()))
- add(Item(R.string.euicc_info_pp_version, info.ppVersion.toString()))
- info.sasAccreditationNumber.trim().takeIf(RE_SAS::matches)
- ?.let { add(Item(R.string.euicc_info_sas_accreditation_number, it.uppercase())) }
-
- val nvramText = buildString {
- append(formatFreeSpace(info.freeNvram))
- append(' ')
- append(getString(R.string.euicc_info_free_nvram_hint))
- }
- add(Item(R.string.euicc_info_free_nvram, nvramText))
+ channel.lpa.euiccInfo2.let { info ->
+ add(Item(R.string.euicc_info_sgp22_version, info?.sgp22Version.toString()))
+ add(Item(R.string.euicc_info_firmware_version, info?.euiccFirmwareVersion.toString()))
+ add(Item(R.string.euicc_info_globalplatform_version, info?.globalPlatformVersion.toString()))
+ add(Item(R.string.euicc_info_pp_version, info?.ppVersion.toString()))
+ add(Item(R.string.euicc_info_sas_accreditation_number, info?.sasAccreditationNumber))
+ add(Item(R.string.euicc_info_free_nvram, info?.freeNvram?.let(::formatFreeSpace)))
}
channel.lpa.euiccInfo2?.euiccCiPKIdListForSigning.orEmpty().let { signers ->
// SGP.28 v1.0, eSIM CI Registration Criteria (Page 5 of 9, 2019-10-24)
@@ -137,20 +122,25 @@ class EuiccInfoActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
// FS.27 v2.0, Security Guidelines for UICC Profiles (Page 25 of 27, 2024-01-30)
// https://www.gsma.com/solutions-and-impact/technologies/security/wp-content/uploads/2024/01/FS.27-Security-Guidelines-for-UICC-Credentials-v2.0-FINAL-23-July.pdf#page=25
val resId = when {
- signers.isEmpty() -> R.string.euicc_info_unknown // the case is not mp, but it's is not common
+ signers.isEmpty() -> R.string.unknown // the case is not mp, but it's is not common
PKID_GSMA_LIVE_CI.any(signers::contains) -> R.string.euicc_info_ci_gsma_live
PKID_GSMA_TEST_CI.any(signers::contains) -> R.string.euicc_info_ci_gsma_test
else -> R.string.euicc_info_ci_unknown
}
add(Item(R.string.euicc_info_ci_type, getString(resId)))
}
- val atr = channel.atr?.encodeHex() ?: getString(R.string.euicc_info_unavailable)
+ val atr = channel.atr?.encodeHex() ?: getString(R.string.information_unavailable)
add(Item(R.string.euicc_info_atr, atr, copiedToastResId = R.string.toast_atr_copied))
}
- @Suppress("SameParameterValue")
private fun formatByBoolean(b: Boolean, res: Pair): String =
- getString(if (b) res.first else res.second)
+ 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)
@@ -177,7 +167,7 @@ class EuiccInfoActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
fun bind(item: Item) {
copiedToastResId = item.copiedToastResId
title.setText(item.titleResId)
- content.text = item.content ?: getString(R.string.euicc_info_unknown)
+ content.text = item.content ?: getString(R.string.unknown)
}
}
diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt
index 016e96f..12995ff 100644
--- a/app-common/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt
+++ b/app-common/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt
@@ -253,7 +253,7 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
if (!isUsb) {
withContext(Dispatchers.Main) {
AlertDialog.Builder(requireContext()).apply {
- setMessage(R.string.profile_switch_did_not_refresh)
+ setMessage(R.string.switch_did_not_refresh)
setPositiveButton(android.R.string.ok) { dialog, _ ->
dialog.dismiss()
requireActivity().finish()
@@ -347,7 +347,6 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
private val profileClassLabel: TextView = root.requireViewById(R.id.profile_class_label)
private val profileClass: TextView = root.requireViewById(R.id.profile_class)
private val profileMenu: ImageButton = root.requireViewById(R.id.profile_menu)
- private val profileSeqNumber: TextView = root.requireViewById(R.id.profile_sequence_number)
init {
iccid.setOnClickListener {
@@ -367,9 +366,7 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
true
}
- profileMenu.setOnClickListener {
- showOptionsMenu()
- }
+ profileMenu.setOnClickListener { showOptionsMenu() }
}
private lateinit var profile: LocalProfileInfo
@@ -380,9 +377,9 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
state.setText(
if (profile.isEnabled) {
- R.string.profile_state_enabled
+ R.string.enabled
} else {
- R.string.profile_state_disabled
+ R.string.disabled
}
)
provider.text = profile.providerName
@@ -399,13 +396,6 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
iccid.transformationMethod = PasswordTransformationMethod.getInstance()
}
- fun setProfileSequenceNumber(index: Int) {
- profileSeqNumber.text = root.context.getString(
- R.string.profile_sequence_number_format,
- index,
- )
- }
-
private fun showOptionsMenu() {
// Prevent users from doing multiple things at once
if (invalid || swipeRefresh.isRefreshing) return
@@ -471,7 +461,6 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
when (holder) {
is ProfileViewHolder -> {
holder.setProfile(profiles[position])
- holder.setProfileSequenceNumber(position + 1)
}
is FooterViewHolder -> {
holder.attach(footerViews[position - profiles.size])
diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/IsdrAidListActivity.kt b/app-common/src/main/java/im/angry/openeuicc/ui/IsdrAidListActivity.kt
deleted file mode 100644
index 022a391..0000000
--- a/app-common/src/main/java/im/angry/openeuicc/ui/IsdrAidListActivity.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-package im.angry.openeuicc.ui
-
-import android.os.Bundle
-import android.text.Editable
-import android.view.Menu
-import android.view.MenuItem
-import android.widget.EditText
-import android.widget.Toast
-import androidx.activity.enableEdgeToEdge
-import androidx.appcompat.app.AppCompatActivity
-import androidx.lifecycle.lifecycleScope
-import im.angry.openeuicc.common.R
-import im.angry.openeuicc.util.preferenceRepository
-import im.angry.openeuicc.util.setupToolbarInsets
-import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.launch
-
-class IsdrAidListActivity : AppCompatActivity() {
- private lateinit var isdrAidListEditor: EditText
-
- override fun onCreate(savedInstanceState: Bundle?) {
- enableEdgeToEdge()
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_isdr_aid_list)
- setSupportActionBar(requireViewById(R.id.toolbar))
- setupToolbarInsets()
- supportActionBar!!.setDisplayHomeAsUpEnabled(true)
-
- isdrAidListEditor = requireViewById(R.id.isdr_aid_list_editor)
-
- lifecycleScope.launch {
- preferenceRepository.isdrAidListFlow.onEach {
- isdrAidListEditor.text = Editable.Factory.getInstance().newEditable(it)
- }.collect()
- }
- }
-
- override fun onCreateOptionsMenu(menu: Menu?): Boolean {
- menuInflater.inflate(R.menu.activity_isdr_aid_list, menu)
- return true
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean =
- when (item.itemId) {
- R.id.save -> {
- lifecycleScope.launch {
- preferenceRepository.isdrAidListFlow.updatePreference(isdrAidListEditor.text.toString())
- Toast.makeText(
- this@IsdrAidListActivity,
- R.string.isdr_aid_list_saved,
- Toast.LENGTH_SHORT
- ).show()
- }
- true
- }
-
- R.id.reset -> {
- lifecycleScope.launch {
- preferenceRepository.isdrAidListFlow.removePreference()
- }
- true
- }
-
- android.R.id.home -> {
- finish()
- true
- }
-
- else -> super.onOptionsItemSelected(item)
- }
-}
\ No newline at end of file
diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/MainActivity.kt b/app-common/src/main/java/im/angry/openeuicc/ui/MainActivity.kt
index b42f4cf..01d0ab2 100644
--- a/app-common/src/main/java/im/angry/openeuicc/ui/MainActivity.kt
+++ b/app-common/src/main/java/im/angry/openeuicc/ui/MainActivity.kt
@@ -174,7 +174,7 @@ open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
// If USB readers exist, add them at the very last
// We use a wrapper fragment to handle logic specific to USB readers
usbDevice?.let {
- val productName = it.productName ?: getString(R.string.channel_type_usb)
+ val productName = it.productName ?: getString(R.string.usb)
newPages.add(Page(EuiccChannelManager.USB_CHANNEL_ID, productName) {
UsbCcidReaderFragment()
})
diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/NotificationsActivity.kt b/app-common/src/main/java/im/angry/openeuicc/ui/NotificationsActivity.kt
index 07d5f13..21a2d40 100644
--- a/app-common/src/main/java/im/angry/openeuicc/ui/NotificationsActivity.kt
+++ b/app-common/src/main/java/im/angry/openeuicc/ui/NotificationsActivity.kt
@@ -60,7 +60,7 @@ class NotificationsActivity: BaseEuiccAccessActivity(), OpenEuiccContextMarker {
// This is slightly different from the MainActivity logic
// due to the length (we don't want to display the full USB product name)
val channelTitle = if (logicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) {
- getString(R.string.channel_type_usb)
+ getString(R.string.usb)
} else {
appContainer.customizableTextProvider.formatInternalChannelName(logicalSlotId)
}
diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt
index 281e625..c588254 100644
--- a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt
+++ b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt
@@ -65,7 +65,7 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment
super.onViewCreated(view, savedInstanceState)
profileRenameNewName.editText!!.setText(currentName)
toolbar.apply {
- setTitle(R.string.profile_rename)
+ setTitle(R.string.rename)
setNavigationOnClickListener {
if (!renaming) dismiss()
}
diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/SettingsFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/SettingsFragment.kt
index 7a717ac..b085286 100644
--- a/app-common/src/main/java/im/angry/openeuicc/ui/SettingsFragment.kt
+++ b/app-common/src/main/java/im/angry/openeuicc/ui/SettingsFragment.kt
@@ -8,7 +8,6 @@ import android.provider.Settings
import android.widget.Toast
import androidx.lifecycle.lifecycleScope
import androidx.preference.CheckBoxPreference
-import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceFragmentCompat
@@ -17,6 +16,7 @@ import im.angry.openeuicc.util.*
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
open class SettingsFragment: PreferenceFragmentCompat() {
private lateinit var developerPref: PreferenceCategory
@@ -34,7 +34,7 @@ open class SettingsFragment: PreferenceFragmentCompat() {
// Show / hide developer preference based on whether it is enabled
lifecycleScope.launch {
preferenceRepository.developerOptionsEnabledFlow
- .onEach(developerPref::setVisible)
+ .onEach { developerPref.isVisible = it }
.collect()
}
@@ -78,18 +78,8 @@ open class SettingsFragment: PreferenceFragmentCompat() {
requirePreference("pref_developer_ignore_tls_certificate")
.bindBooleanFlow(preferenceRepository.ignoreTLSCertificateFlow)
- requirePreference("pref_developer_refresh_after_switch")
- .bindBooleanFlow(preferenceRepository.refreshAfterSwitchFlow)
-
requirePreference("pref_developer_euicc_memory_reset")
.bindBooleanFlow(preferenceRepository.euiccMemoryResetFlow)
-
- requirePreference("pref_developer_es10x_mss")
- .bindIntFlow(preferenceRepository.es10xMssFlow, 63)
-
- requirePreference("pref_developer_isdr_aid_list").apply {
- intent = Intent(requireContext(), IsdrAidListActivity::class.java)
- }
}
protected fun requirePreference(key: CharSequence) =
@@ -103,53 +93,51 @@ open class SettingsFragment: PreferenceFragmentCompat() {
@Suppress("UNUSED_PARAMETER")
private fun onAppVersionClicked(pref: Preference): Boolean {
if (developerPref.isVisible) return false
-
val now = System.currentTimeMillis()
- numClicks = if (now - lastClickTimestamp >= 1000) 1 else numClicks + 1
+ if (now - lastClickTimestamp >= 1000) {
+ numClicks = 1
+ } else {
+ numClicks++
+ }
lastClickTimestamp = now
- lifecycleScope.launch {
- preferenceRepository.developerOptionsEnabledFlow.updatePreference(numClicks >= 7)
+ if (numClicks == 7) {
+ lifecycleScope.launch {
+ preferenceRepository.developerOptionsEnabledFlow.updatePreference(true)
+
+ lastToast?.cancel()
+ Toast.makeText(
+ requireContext(),
+ R.string.developer_options_enabled,
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ } else if (numClicks > 1) {
+ lastToast?.cancel()
+ lastToast = Toast.makeText(
+ requireContext(),
+ getString(R.string.developer_options_steps, 7 - numClicks),
+ Toast.LENGTH_SHORT
+ )
+ lastToast!!.show()
}
- val toastText = when {
- numClicks == 7 -> getString(R.string.developer_options_enabled)
- numClicks > 1 -> getString(R.string.developer_options_steps, 7 - numClicks)
- else -> return true
- }
-
- lastToast?.cancel()
- lastToast = Toast.makeText(requireContext(), toastText, Toast.LENGTH_SHORT)
- lastToast!!.show()
return true
}
protected fun CheckBoxPreference.bindBooleanFlow(flow: PreferenceFlowWrapper) {
lifecycleScope.launch {
- flow.collect(::setChecked)
+ flow.collect { isChecked = it }
}
setOnPreferenceChangeListener { _, newValue ->
- lifecycleScope.launch {
+ runBlocking {
flow.updatePreference(newValue as Boolean)
}
true
}
}
- private fun ListPreference.bindIntFlow(flow: PreferenceFlowWrapper, defaultValue: Int) {
- lifecycleScope.launch {
- flow.collect { value = it.toString() }
- }
-
- setOnPreferenceChangeListener { _, newValue ->
- lifecycleScope.launch {
- flow.updatePreference((newValue as String).toIntOrNull() ?: defaultValue)
- }
- true
- }
- }
-
protected fun mergePreferenceOverlay(overlayKey: String, targetKey: String) {
val overlayCat = requirePreference(overlayKey)
val targetCat = requirePreference(targetKey)
diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt
index 6574645..a9f868f 100644
--- a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt
+++ b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt
@@ -3,7 +3,6 @@ package im.angry.openeuicc.ui.wizard
import android.app.assist.AssistContent
import android.os.Bundle
import android.view.View
-import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
import android.widget.Button
import android.widget.ProgressBar
@@ -123,8 +122,8 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() {
// If we get an LPA string from deep-link intents, extract from there.
// Note that `onRestoreInstanceState` could override this with user input,
// but that _is_ the desired behavior.
- val uri = intent.data ?: return
- if (uri.scheme.contentEquals("lpa", ignoreCase = true)) {
+ val uri = intent.data
+ if (uri?.scheme == "lpa") {
val parsed = LPAString.parse(uri.schemeSpecificPart)
state.smdp = parsed.address
state.matchingId = parsed.matchingId
@@ -252,14 +251,6 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() {
supportFragmentManager.beginTransaction().setCustomAnimations(enterAnim, exitAnim)
.replace(R.id.step_fragment_container, nextFrag)
.commit()
-
- // Sync screen on state
- if (nextFrag.keepScreenOn) {
- window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
- } else {
- window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
- }
-
refreshButtons()
}
@@ -289,8 +280,6 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() {
protected val state: DownloadWizardState
get() = (requireActivity() as DownloadWizardActivity).state
- open val keepScreenOn = false
-
abstract val hasNext: Boolean
abstract val hasPrev: Boolean
abstract fun createNextFragment(): DownloadWizardStepFragment?
diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardDetailsFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardDetailsFragment.kt
index 1c69de5..402e7a5 100644
--- a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardDetailsFragment.kt
+++ b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardDetailsFragment.kt
@@ -1,9 +1,11 @@
package im.angry.openeuicc.ui.wizard
import android.os.Bundle
+import android.util.Patterns
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.widget.Toast
import androidx.core.widget.addTextChangedListener
import com.google.android.material.textfield.TextInputLayout
import im.angry.openeuicc.common.R
@@ -84,34 +86,10 @@ class DownloadWizardDetailsFragment : DownloadWizardActivity.DownloadWizardStepF
}
private fun updateInputCompleteness() {
- inputComplete = isValidAddress(smdp.editText!!.text)
+ inputComplete = Patterns.DOMAIN_NAME.matcher(smdp.editText!!.text).matches()
if (state.confirmationCodeRequired) {
inputComplete = inputComplete && confirmationCode.editText!!.text.isNotEmpty()
}
refreshButtons()
}
-}
-
-private fun isValidAddress(input: CharSequence): Boolean {
- if (!input.contains('.')) return false
- var fqdn = input
- var port = 443
- if (input.contains(':')) {
- val portIndex = input.lastIndexOf(':')
- fqdn = input.substring(0, portIndex)
- port = input.substring(portIndex + 1, input.length).toIntOrNull(10) ?: 0
- }
- // see https://en.wikipedia.org/wiki/Port_(computer_networking)
- if (port < 1 || port > 0xffff) return false
- // see https://en.wikipedia.org/wiki/Fully_qualified_domain_name
- if (fqdn.isEmpty() || fqdn.length > 255) return false
- for (part in fqdn.split('.')) {
- if (part.isEmpty() || part.length > 64) return false
- if (part.first() == '-' || part.last() == '-') return false
- for (c in part) {
- if (c.isLetterOrDigit() || c == '-') continue
- return false
- }
- }
- return true
}
\ No newline at end of file
diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardDiagnosticsFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardDiagnosticsFragment.kt
index 3841868..e282196 100644
--- a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardDiagnosticsFragment.kt
+++ b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardDiagnosticsFragment.kt
@@ -8,7 +8,6 @@ import android.view.ViewGroup
import android.widget.TextView
import im.angry.openeuicc.common.R
import im.angry.openeuicc.util.*
-import org.json.JSONObject
import java.util.Date
class DownloadWizardDiagnosticsFragment : DownloadWizardActivity.DownloadWizardStepFragment() {
@@ -87,10 +86,9 @@ class DownloadWizardDiagnosticsFragment : DownloadWizardActivity.DownloadWizardS
ret.appendLine()
val str = resp.data.decodeToString(throwOnInvalidSequence = false)
-
ret.appendLine(
if (str.startsWith('{')) {
- JSONObject(str).toString(2)
+ str.prettyPrintJson()
} else {
str
}
diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardProgressFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardProgressFragment.kt
index 0048190..1b816d4 100644
--- a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardProgressFragment.kt
+++ b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardProgressFragment.kt
@@ -7,7 +7,6 @@ import android.view.ViewGroup
import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.TextView
-import androidx.annotation.StringRes
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
@@ -43,24 +42,23 @@ class DownloadWizardProgressFragment : DownloadWizardActivity.DownloadWizardStep
}
private data class ProgressItem(
- @StringRes val titleRes: Int,
- var state: ProgressState = ProgressState.NotStarted,
- var errorMessage: SimplifiedErrorMessages? = null,
+ val titleRes: Int,
+ var state: ProgressState
)
private val progressItems = arrayOf(
- ProgressItem(R.string.download_wizard_progress_step_preparing),
- ProgressItem(R.string.download_wizard_progress_step_connecting),
- ProgressItem(R.string.download_wizard_progress_step_authenticating),
- ProgressItem(R.string.download_wizard_progress_step_downloading),
- ProgressItem(R.string.download_wizard_progress_step_finalizing)
+ ProgressItem(R.string.download_wizard_progress_step_preparing, ProgressState.NotStarted),
+ ProgressItem(R.string.download_wizard_progress_step_connecting, ProgressState.NotStarted),
+ ProgressItem(
+ R.string.download_wizard_progress_step_authenticating,
+ ProgressState.NotStarted
+ ),
+ ProgressItem(R.string.download_wizard_progress_step_downloading, ProgressState.NotStarted),
+ ProgressItem(R.string.download_wizard_progress_step_finalizing, ProgressState.NotStarted)
)
private val adapter = ProgressItemAdapter()
- // We don't want to turn off the screen during a download
- override val keepScreenOn = true
-
private var isDone = false
override val hasNext: Boolean
@@ -121,13 +119,8 @@ class DownloadWizardProgressFragment : DownloadWizardActivity.DownloadWizardStep
// Change the state of the last InProgress item to success (or error)
progressItems.forEachIndexed { index, progressItem ->
if (progressItem.state == ProgressState.InProgress) {
- if (state.downloadError == null) {
- progressItem.state = ProgressState.Done
- } else {
- progressItem.state = ProgressState.Error
- progressItem.errorMessage =
- SimplifiedErrorMessages.fromDownloadError(state.downloadError!!)
- }
+ progressItem.state =
+ if (state.downloadError == null) ProgressState.Done else ProgressState.Error
}
adapter.notifyItemChanged(index)
@@ -137,8 +130,9 @@ class DownloadWizardProgressFragment : DownloadWizardActivity.DownloadWizardStep
refreshButtons()
}
- is EuiccChannelManagerService.ForegroundTaskState.InProgress ->
+ is EuiccChannelManagerService.ForegroundTaskState.InProgress -> {
updateProgress(it.progress)
+ }
else -> {}
}
@@ -200,15 +194,9 @@ class DownloadWizardProgressFragment : DownloadWizardActivity.DownloadWizardStep
private val progressBar =
root.requireViewById(R.id.download_progress_icon_progress)
private val icon = root.requireViewById(R.id.download_progress_icon)
- private val errorTitle =
- root.requireViewById(R.id.download_progress_item_error_title)
- private val errorSuggestion =
- root.requireViewById(R.id.download_progress_item_error_suggestion)
fun bind(item: ProgressItem) {
title.text = getString(item.titleRes)
- errorTitle.visibility = View.GONE
- errorSuggestion.visibility = View.GONE
when (item.state) {
ProgressState.NotStarted -> {
@@ -231,15 +219,6 @@ class DownloadWizardProgressFragment : DownloadWizardActivity.DownloadWizardStep
progressBar.visibility = View.GONE
icon.setImageResource(R.drawable.ic_error_outline)
icon.visibility = View.VISIBLE
-
- item.errorMessage?.titleResId?.let {
- errorTitle.visibility = View.VISIBLE
- errorTitle.text = getString(it)
- }
- item.errorMessage?.suggestResId?.let {
- errorSuggestion.visibility = View.VISIBLE
- errorSuggestion.text = getString(it)
- }
}
}
}
diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt
index 8097058..28bc9f0 100644
--- a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt
+++ b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardSlotSelectFragment.kt
@@ -19,6 +19,7 @@ import im.angry.openeuicc.util.*
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
+import net.typeblog.lpac_jni.LocalProfileInfo
class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardStepFragment() {
companion object {
@@ -186,12 +187,12 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt
}
title.text = if (item.logicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) {
- item.intrinsicChannelName ?: root.context.getString(R.string.channel_type_usb)
+ item.intrinsicChannelName ?: root.context.getString(R.string.usb)
} else {
appContainer.customizableTextProvider.formatInternalChannelName(item.logicalSlotId)
}
eID.text = item.eID
- activeProfile.text = item.enabledProfileName ?: root.context.getString(R.string.profile_no_enabled_profile)
+ activeProfile.text = item.enabledProfileName ?: root.context.getString(R.string.unknown)
freeSpace.text = formatFreeSpace(item.freeSpace)
checkBox.isChecked = adapter.currentSelectedIdx == idx
}
diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/SimplifiedErrorMessages.kt b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/SimplifiedErrorMessages.kt
deleted file mode 100644
index 8ce5740..0000000
--- a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/SimplifiedErrorMessages.kt
+++ /dev/null
@@ -1,154 +0,0 @@
-package im.angry.openeuicc.ui.wizard
-
-import androidx.annotation.StringRes
-import im.angry.openeuicc.common.R
-import net.typeblog.lpac_jni.LocalProfileAssistant
-import org.json.JSONObject
-import java.net.NoRouteToHostException
-import java.net.PortUnreachableException
-import java.net.SocketException
-import java.net.SocketTimeoutException
-import java.net.UnknownHostException
-import javax.net.ssl.SSLException
-
-enum class SimplifiedErrorMessages(
- @StringRes val titleResId: Int,
- @StringRes val suggestResId: Int?
-) {
- ICCIDAlreadyInUse(
- R.string.download_wizard_error_iccid_already,
- R.string.download_wizard_error_suggest_profile_installed
- ),
- InsufficientMemory(
- R.string.download_wizard_error_insufficient_memory,
- R.string.download_wizard_error_suggest_insufficient_memory
- ),
- UnsupportedProfile(
- R.string.download_wizard_error_unsupported_profile,
- null
- ),
- CardInternalError(
- R.string.download_wizard_error_card_internal_error,
- null
- ),
- EIDNotSupported(
- R.string.download_wizard_error_eid_not_supported,
- R.string.download_wizard_error_suggest_contact_carrier
- ),
- EIDMismatch(
- R.string.download_wizard_error_eid_mismatch,
- R.string.download_wizard_error_suggest_contact_reissue
- ),
- UnreleasedProfile(
- R.string.download_wizard_error_profile_unreleased,
- R.string.download_wizard_error_suggest_contact_reissue
- ),
- MatchingIDRefused(
- R.string.download_wizard_error_matching_id_refused,
- R.string.download_wizard_error_suggest_contact_carrier
- ),
- ProfileRetriesExceeded(
- R.string.download_wizard_error_profile_retries_exceeded,
- R.string.download_wizard_error_suggest_contact_carrier
- ),
- ConfirmationCodeMissing(
- R.string.download_wizard_error_confirmation_code_missing,
- R.string.download_wizard_error_suggest_contact_carrier
- ),
- ConfirmationCodeRefused(
- R.string.download_wizard_error_confirmation_code_refused,
- R.string.download_wizard_error_suggest_contact_carrier
- ),
- ConfirmationCodeRetriesExceeded(
- R.string.download_wizard_error_confirmation_code_retries_exceeded,
- R.string.download_wizard_error_suggest_contact_carrier
- ),
- ProfileExpired(
- R.string.download_wizard_error_profile_expired,
- R.string.download_wizard_error_suggest_contact_carrier
- ),
- UnknownHost(
- R.string.download_wizard_error_unknown_hostname,
- null
- ),
- NetworkUnreachable(
- R.string.download_wizard_error_network_unreachable,
- R.string.download_wizard_error_suggest_network_unreachable
- ),
- TLSError(
- R.string.download_wizard_error_tls_certificate,
- null
- );
-
- companion object {
- private val httpErrors = buildMap {
- // Stage: AuthenticateClient
- put("8.1" to "4.8", InsufficientMemory)
- put("8.1.1" to "2.1", EIDNotSupported)
- put("8.1.1" to "3.8", EIDMismatch)
- put("8.2" to "1.2", UnreleasedProfile)
- put("8.2.6" to "3.8", MatchingIDRefused)
- put("8.8.5" to "6.4", ProfileRetriesExceeded)
-
- // Stage: GetBoundProfilePackage
- put("8.2.7" to "2.2", ConfirmationCodeMissing)
- put("8.2.7" to "3.8", ConfirmationCodeRefused)
- put("8.2.7" to "6.4", ConfirmationCodeRetriesExceeded)
-
- // Stage: AuthenticateClient, GetBoundProfilePackage
- put("8.8.5" to "4.10", ProfileExpired)
- }
-
- fun fromDownloadError(exc: LocalProfileAssistant.ProfileDownloadException) = when {
- exc.lpaErrorReason != "ES10B_ERROR_REASON_UNDEFINED" -> fromLPAErrorReason(exc.lpaErrorReason)
- exc.lastHttpResponse?.rcode == 200 -> fromHTTPResponse(exc.lastHttpResponse!!)
- exc.lastHttpException != null -> fromHTTPException(exc.lastHttpException!!)
- exc.lastApduResponse != null -> fromAPDUResponse(exc.lastApduResponse!!)
- else -> null
- }
-
- private fun fromLPAErrorReason(reason: String) = when (reason) {
- "ES10B_ERROR_REASON_UNSUPPORTED_CRT_VALUES" -> UnsupportedProfile
- "ES10B_ERROR_REASON_UNSUPPORTED_REMOTE_OPERATION_TYPE" -> UnsupportedProfile
- "ES10B_ERROR_REASON_UNSUPPORTED_PROFILE_CLASS" -> UnsupportedProfile
- "ES10B_ERROR_REASON_INSTALL_FAILED_DUE_TO_ICCID_ALREADY_EXISTS_ON_EUICC" -> ICCIDAlreadyInUse
- "ES10B_ERROR_REASON_INSTALL_FAILED_DUE_TO_INSUFFICIENT_MEMORY_FOR_PROFILE" -> InsufficientMemory
- "ES10B_ERROR_REASON_INSTALL_FAILED_DUE_TO_INTERRUPTION" -> CardInternalError
- "ES10B_ERROR_REASON_INSTALL_FAILED_DUE_TO_PE_PROCESSING_ERROR" -> CardInternalError
- else -> null
- }
-
- private fun fromHTTPResponse(httpResponse: net.typeblog.lpac_jni.HttpInterface.HttpResponse): SimplifiedErrorMessages? {
- if (httpResponse.data.first().toInt() != '{'.code) return null
- val response = JSONObject(httpResponse.data.decodeToString())
- val statusCodeData = response.optJSONObject("header")
- ?.optJSONObject("functionExecutionStatus")
- ?.optJSONObject("statusCodeData")
- ?: return null
- val subjectCode = statusCodeData.optString("subjectCode")
- val reasonCode = statusCodeData.optString("reasonCode")
- return httpErrors[subjectCode to reasonCode]
- }
-
- private fun fromHTTPException(exc: Exception) = when (exc) {
- is SSLException -> TLSError
- is UnknownHostException -> UnknownHost
- is NoRouteToHostException -> NetworkUnreachable
- is PortUnreachableException -> NetworkUnreachable
- is SocketTimeoutException -> NetworkUnreachable
- is SocketException -> exc.message
- ?.contains("Connection reset", ignoreCase = true)
- ?.let { if (it) NetworkUnreachable else null }
-
- else -> null
- }
-
- private fun fromAPDUResponse(resp: ByteArray): SimplifiedErrorMessages? {
- val isSuccess = resp.size >= 2 &&
- resp[resp.size - 2] == 0x90.toByte() &&
- resp[resp.size - 1] == 0x00.toByte()
- if (isSuccess) return null
- return CardInternalError
- }
- }
-}
diff --git a/app-common/src/main/java/im/angry/openeuicc/util/LPAString.kt b/app-common/src/main/java/im/angry/openeuicc/util/LPAString.kt
index 63a81f1..20956fb 100644
--- a/app-common/src/main/java/im/angry/openeuicc/util/LPAString.kt
+++ b/app-common/src/main/java/im/angry/openeuicc/util/LPAString.kt
@@ -8,15 +8,15 @@ data class LPAString(
) {
companion object {
fun parse(input: String): LPAString {
- var token = input
- if (token.startsWith("LPA:", ignoreCase = true)) token = token.drop(4)
- val components = token.split('$').map { it.trim().ifBlank { null } }
- require(components.getOrNull(0) == "1") { "Invalid AC_Format" }
+ val components = input.removePrefix("LPA:").split('$')
+ if (components.size < 2 || components[0] != "1") {
+ throw IllegalArgumentException("Invalid activation code format")
+ }
return LPAString(
- requireNotNull(components.getOrNull(1)) { "SM-DP+ is required" },
- components.getOrNull(2),
- components.getOrNull(3),
- components.getOrNull(4) == "1"
+ address = components[1].trim(),
+ matchingId = components.getOrNull(2)?.trim()?.ifBlank { null },
+ oid = components.getOrNull(3)?.trim()?.ifBlank { null },
+ confirmationCodeRequired = components.getOrNull(4)?.trim() == "1"
)
}
}
diff --git a/app-common/src/main/java/im/angry/openeuicc/util/PreferenceUtils.kt b/app-common/src/main/java/im/angry/openeuicc/util/PreferenceUtils.kt
index 2fef3db..34d1cfd 100644
--- a/app-common/src/main/java/im/angry/openeuicc/util/PreferenceUtils.kt
+++ b/app-common/src/main/java/im/angry/openeuicc/util/PreferenceUtils.kt
@@ -5,14 +5,11 @@ import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
-import androidx.datastore.preferences.core.intPreferencesKey
-import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import androidx.fragment.app.Fragment
import im.angry.openeuicc.OpenEuiccApplication
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
-import java.util.Base64
private val Context.dataStore: DataStore by preferencesDataStore(name = "prefs")
@@ -34,39 +31,9 @@ internal object PreferenceKeys {
// ---- Developer Options ----
val DEVELOPER_OPTIONS_ENABLED = booleanPreferencesKey("developer_options_enabled")
- val REFRESH_AFTER_SWITCH = booleanPreferencesKey("refresh_after_switch")
val UNFILTERED_PROFILE_LIST = booleanPreferencesKey("unfiltered_profile_list")
val IGNORE_TLS_CERTIFICATE = booleanPreferencesKey("ignore_tls_certificate")
val EUICC_MEMORY_RESET = booleanPreferencesKey("euicc_memory_reset")
- val ISDR_AID_LIST = stringPreferencesKey("isdr_aid_list")
- val ES10X_MSS = intPreferencesKey("es10x_mss")
-}
-
-const val EUICC_DEFAULT_ISDR_AID = "A0000005591010FFFFFFFF8900000100"
-
-internal object PreferenceConstants {
- val DEFAULT_AID_LIST = """
- # One AID per line. Comment lines start with #.
- # Refs:
-
- # eUICC standard
- $EUICC_DEFAULT_ISDR_AID
-
- # ESTKme AUX (deprecated, use SE0 instead)
- A06573746B6D65FFFFFFFF4953442D52
-
- # ESTKme SE0
- A06573746B6D65FFFF4953442D522030
-
- # eSIM.me
- A0000005591010000000008900000300
-
- # 5ber.eSIM
- A0000005591010FFFFFFFF8900050500
-
- # Xesim
- A0000005591010FFFFFFFF8900000177
- """.trimIndent()
}
open class PreferenceRepository(private val context: Context) {
@@ -81,51 +48,27 @@ open class PreferenceRepository(private val context: Context) {
val verboseLoggingFlow = bindFlow(PreferenceKeys.VERBOSE_LOGGING, false)
// ---- Developer Options ----
- val refreshAfterSwitchFlow = bindFlow(PreferenceKeys.REFRESH_AFTER_SWITCH, true)
val developerOptionsEnabledFlow = bindFlow(PreferenceKeys.DEVELOPER_OPTIONS_ENABLED, false)
val unfilteredProfileListFlow = bindFlow(PreferenceKeys.UNFILTERED_PROFILE_LIST, false)
val ignoreTLSCertificateFlow = bindFlow(PreferenceKeys.IGNORE_TLS_CERTIFICATE, false)
val euiccMemoryResetFlow = bindFlow(PreferenceKeys.EUICC_MEMORY_RESET, false)
- val isdrAidListFlow = bindFlow(
- PreferenceKeys.ISDR_AID_LIST,
- PreferenceConstants.DEFAULT_AID_LIST,
- { Base64.getEncoder().encodeToString(it.encodeToByteArray()) },
- { Base64.getDecoder().decode(it).decodeToString() })
- val es10xMssFlow = bindFlow(PreferenceKeys.ES10X_MSS, 63)
- protected fun bindFlow(
- key: Preferences.Key,
- defaultValue: T,
- encoder: (T) -> T = { it },
- decoder: (T) -> T = { it }
- ): PreferenceFlowWrapper =
- PreferenceFlowWrapper(context, key, defaultValue, encoder, decoder)
+ protected fun bindFlow(key: Preferences.Key, defaultValue: T): PreferenceFlowWrapper =
+ PreferenceFlowWrapper(context, key, defaultValue)
}
class PreferenceFlowWrapper private constructor(
private val context: Context,
private val key: Preferences.Key,
- inner: Flow,
- private val encoder: (T) -> T,
+ inner: Flow
) : Flow by inner {
- internal constructor(
- context: Context,
- key: Preferences.Key,
- defaultValue: T,
- encoder: (T) -> T,
- decoder: (T) -> T
- ) : this(
+ internal constructor(context: Context, key: Preferences.Key, defaultValue: T) : this(
context,
key,
- context.dataStore.data.map { it[key]?.let(decoder) ?: defaultValue },
- encoder
+ context.dataStore.data.map { it[key] ?: defaultValue }
)
suspend fun updatePreference(value: T) {
- context.dataStore.edit { it[key] = encoder(value) }
+ context.dataStore.edit { it[key] = value }
}
-
- suspend fun removePreference() {
- context.dataStore.edit { it.remove(key) }
- }
-}
+}
\ No newline at end of file
diff --git a/app-common/src/main/java/im/angry/openeuicc/util/StringUtils.kt b/app-common/src/main/java/im/angry/openeuicc/util/StringUtils.kt
index 57d150b..8d72462 100644
--- a/app-common/src/main/java/im/angry/openeuicc/util/StringUtils.kt
+++ b/app-common/src/main/java/im/angry/openeuicc/util/StringUtils.kt
@@ -1,7 +1,7 @@
package im.angry.openeuicc.util
fun String.decodeHex(): ByteArray {
- require(length % 2 == 0) { "Must have an even length" }
+ check(length % 2 == 0) { "Must have an even length" }
val decodedLength = length / 2
val out = ByteArray(decodedLength)
@@ -29,15 +29,72 @@ fun formatFreeSpace(size: Int): String =
"$size B"
}
-/**
- * Decode a list of potential ISDR AIDs, one per line. Lines starting with '#' are ignored.
- * If none is found, at least EUICC_DEFAULT_ISDR_AID is returned
- */
-fun parseIsdrAidList(s: String): List =
- s.split('\n')
- .map(String::trim)
- .filter { !it.startsWith('#') }
- .map(String::trim)
- .filter(String::isNotEmpty)
- .mapNotNull { runCatching(it::decodeHex).getOrNull() }
- .ifEmpty { listOf(EUICC_DEFAULT_ISDR_AID.decodeHex()) }
+fun String.prettyPrintJson(): String {
+ val ret = StringBuilder()
+ var inQuotes = false
+ var escaped = false
+ val indentSymbolStack = ArrayDeque()
+
+ val addNewLine = {
+ ret.append('\n')
+ repeat(indentSymbolStack.size) {
+ ret.append('\t')
+ }
+ }
+
+ var lastChar = ' '
+
+ for (c in this) {
+ when {
+ !inQuotes && (c == '{' || c == '[') -> {
+ ret.append(c)
+ indentSymbolStack.addLast(c)
+ addNewLine()
+ }
+
+ !inQuotes && (c == '}' || c == ']') -> {
+ indentSymbolStack.removeLast()
+ if (lastChar != ',') {
+ addNewLine()
+ }
+ ret.append(c)
+ }
+
+ !inQuotes && c == ',' -> {
+ ret.append(c)
+ addNewLine()
+ }
+
+ !inQuotes && c == ':' -> {
+ ret.append(c)
+ ret.append(' ')
+ }
+
+ inQuotes && c == '\\' -> {
+ ret.append(c)
+ escaped = true
+ continue
+ }
+
+ !escaped && c == '"' -> {
+ ret.append(c)
+ inQuotes = !inQuotes
+ }
+
+ !inQuotes && c == ' ' -> {
+ // Do nothing -- we ignore spaces outside of quotes by default
+ // This is to ensure predictable formatting
+ }
+
+ else -> ret.append(c)
+ }
+
+ if (escaped) {
+ escaped = false
+ }
+
+ lastChar = c
+ }
+
+ return ret.toString()
+}
\ No newline at end of file
diff --git a/app-common/src/main/java/im/angry/openeuicc/util/UiUtils.kt b/app-common/src/main/java/im/angry/openeuicc/util/UiUtils.kt
index c7c859d..a73d7fe 100644
--- a/app-common/src/main/java/im/angry/openeuicc/util/UiUtils.kt
+++ b/app-common/src/main/java/im/angry/openeuicc/util/UiUtils.kt
@@ -102,8 +102,8 @@ fun T.setupLogSaving(
AlertDialog.Builder(context).apply {
setMessage(R.string.logs_saved_message)
- setNegativeButton(android.R.string.cancel) { _, _ -> }
- setPositiveButton(android.R.string.ok) { _, _ ->
+ setNegativeButton(R.string.no) { _, _ -> }
+ setPositiveButton(R.string.yes) { _, _ ->
val intent = Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
clipData = ClipData.newUri(context.contentResolver, lastFileName, uri)
diff --git a/app-common/src/main/java/im/angry/openeuicc/util/Utils.kt b/app-common/src/main/java/im/angry/openeuicc/util/Utils.kt
index 046657f..5a559f9 100644
--- a/app-common/src/main/java/im/angry/openeuicc/util/Utils.kt
+++ b/app-common/src/main/java/im/angry/openeuicc/util/Utils.kt
@@ -54,9 +54,6 @@ interface OpenEuiccContextMarker {
val appContainer: AppContainer
get() = openEuiccApplication.appContainer
- val preferenceRepository: PreferenceRepository
- get() = appContainer.preferenceRepository
-
val telephonyManager: TelephonyManager
get() = appContainer.telephonyManager
}
diff --git a/app-common/src/main/res/layout/download_progress_item.xml b/app-common/src/main/res/layout/download_progress_item.xml
index c59673b..f1d0852 100644
--- a/app-common/src/main/res/layout/download_progress_item.xml
+++ b/app-common/src/main/res/layout/download_progress_item.xml
@@ -1,32 +1,30 @@
+ android:layout_height="wrap_content"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/download_progress_icon_container"
+ app:layout_constrainedWidth="true"
+ app:layout_constraintHorizontal_bias="0.0" />
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent">
-
-
-
-
\ No newline at end of file
diff --git a/app-common/src/main/res/layout/euicc_profile.xml b/app-common/src/main/res/layout/euicc_profile.xml
index 021c53b..58d55ab 100644
--- a/app-common/src/main/res/layout/euicc_profile.xml
+++ b/app-common/src/main/res/layout/euicc_profile.xml
@@ -54,7 +54,7 @@
-
-
diff --git a/app-common/src/main/res/layout/fragment_euicc.xml b/app-common/src/main/res/layout/fragment_euicc.xml
index c5fde7b..4ae7523 100644
--- a/app-common/src/main/res/layout/fragment_euicc.xml
+++ b/app-common/src/main/res/layout/fragment_euicc.xml
@@ -27,7 +27,6 @@
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
- android:contentDescription="@string/profile_download"
android:src="@drawable/ic_add"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
diff --git a/app-common/src/main/res/menu/activity_isdr_aid_list.xml b/app-common/src/main/res/menu/activity_isdr_aid_list.xml
deleted file mode 100644
index 99492d6..0000000
--- a/app-common/src/main/res/menu/activity_isdr_aid_list.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
\ No newline at end of file
diff --git a/app-common/src/main/res/menu/activity_main.xml b/app-common/src/main/res/menu/activity_main.xml
index c15663f..0e00292 100644
--- a/app-common/src/main/res/menu/activity_main.xml
+++ b/app-common/src/main/res/menu/activity_main.xml
@@ -3,7 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
diff --git a/app-common/src/main/res/menu/activity_notifications.xml b/app-common/src/main/res/menu/activity_notifications.xml
index b80e06e..87f96a6 100644
--- a/app-common/src/main/res/menu/activity_notifications.xml
+++ b/app-common/src/main/res/menu/activity_notifications.xml
@@ -4,6 +4,6 @@
\ No newline at end of file
diff --git a/app-common/src/main/res/menu/fragment_profile_rename.xml b/app-common/src/main/res/menu/fragment_profile_rename.xml
index f55c56c..bde850f 100644
--- a/app-common/src/main/res/menu/fragment_profile_rename.xml
+++ b/app-common/src/main/res/menu/fragment_profile_rename.xml
@@ -4,6 +4,6 @@
\ No newline at end of file
diff --git a/app-common/src/main/res/menu/profile_options.xml b/app-common/src/main/res/menu/profile_options.xml
index 60722d6..6add53d 100644
--- a/app-common/src/main/res/menu/profile_options.xml
+++ b/app-common/src/main/res/menu/profile_options.xml
@@ -2,18 +2,18 @@
\ No newline at end of file
diff --git a/app-common/src/main/res/values-ja/strings.xml b/app-common/src/main/res/values-ja/strings.xml
index b171972..1fd361c 100644
--- a/app-common/src/main/res/values-ja/strings.xml
+++ b/app-common/src/main/res/values-ja/strings.xml
@@ -2,28 +2,26 @@
このアプリでアクセスできるリムーバブル eUICC カードがデバイス上で検出されていません。互換性のあるカード挿入または USB リーダーを接続してください。
この eSIM にはプロファイルがありません。
- ヘルプ
- スロットを再読み込み
- 不明
+ 不明
+ 情報がありません
+ ヘルプ
+ スロットを再読み込み
論理スロット %d
-
- 有効化済み
- 無効化済み
- プロバイダー:
+ 有効済み
+ 無効済み
+ プロバイダー:
クラス:
テスト中
プロビジョニング
稼働中
- 有効化
- 無効化
- 削除
- 名前を変更
- eSIM チップがプロファイルの切り替えの待機中にタイムアウトしました。これはデバイスのモデムファームウェアのバグの可能性があります。機内モードに切り替えるかアプリを再起動、デバイスを再起動してください。
- 操作は成功しましたが、デバイスのモデムが更新を拒否しました。新しいプロファイルを使用するには機内モードに切り替えるか、再起動する必要があります。
+ 有効化
+ 無効化
+ 削除
+ 名前を変更
+ eSIM チップがプロファイルの切り替えの待機中にタイムアウトしました。これはデバイスのモデムファームウェアのバグの可能性があります。機内モードに切り替えるかアプリを再起動、デバイスを再起動してください。
+ 操作は成功しましたが、デバイスのモデムが更新を拒否しました。新しいプロファイルを使用するには機内モードに切り替えるか、再起動する必要があります。
新しい eSIM プロファイルに切り替えることができません。
確認文字列が一致しません
- 確認文字列が一致しません
- このチップは消去されました
ICCID をクリップボードにコピーしました
シリアル番号をクリップボードにコピーしました
EID をクリップボードにコピーしました
@@ -40,16 +38,14 @@
eSIM プロファイルの削除に失敗しました
eSIM プロファイルを切り替え中
eSIM プロファイルの切り替えに失敗しました
- eSIM チップを消去中
- eSIM チップの消去に失敗しました
新しい eSIM
サーバー (RSP / SM-DP+)
アクティベーションコード
確認コード (オプション)
確認コード (必須)
IMEI (オプション)
- 残りの容量が少量です
- 残り容量が少ないため、このプロファイルのダウンロードに失敗する可能性があります。
+ 残り容量が少ない
+ 残り容量が少ないため、ダウンロードに失敗する可能性があります。
クリップボードに LPA コードがありません
解析できません
QR コードまたはクリップボードの内容を LPA コードとして解析できませんでした。
@@ -87,27 +83,6 @@
最終の APDU 例外:
保存
「%s」での診断
- この eSIM プロファイルはすでに eSIM チップに存在します。
- eSIM チップにはプロファイルをダウンロードするのに必要なメモリが残っていません。
- この eSIM プロファイルは、eSIM チップではサポートされていません。
- eSIM チップでエラーが発生しました。
- 使用しているデバイスまたは eSIM チップの EID は、通信事業者によってサポートされていません。
- この eSIM プロファイルは、別のデバイスにダウンロードされています。
- この eSIM プロファイルは取り消されました。
- アクティベーションコードが無効です。
- eSIM プロファイルのダウンロード試行回数の上限を超えました。
- このプロファイルをダウンロードするには確認コードが必要です。
- 入力した確認コードは無効です。
- この eSIM プロファイルは有効期限が切れています。
- 確認コードのダウンロード試行回数の上限を超えました。
- 不明な SM-DP+ アドレス
- ネットワークにアクセスできません
- TLS 証明書エラー、この eSIM プロファイルはサポートされていません
- ダウンロード済みの eSIM プロファイルを再インストールしようとしています
- 未使用の eSIM プロファイルをいくつか削除して、再度お試しください
- サポートについては、通信事業者にお問い合わせください。
- この eSIM プロファイルを再発行するには、通信事業者にお問い合わせください。
- 別のネットワークに接続後 (例: Wi-Fi とデータを切り替え) を行った後に再度お試しください。
ログは共有したパスに保存されました。別のアプリで共有しますか?
新しいニックネーム
ニックネームを UTF-8 にエンコードできませんでした
@@ -121,8 +96,8 @@
eSIM プロファイルはダウンロードや削除、有効化や無効化されたときに通信事業者に通知を送信できます。送信されるこれらの通知のキューはここにリストされます。\n\n設定では、各タイプの通知を自動的に送信するかどうかを指定できます。通知が送信された場合でもキューのスペースが不足していない限り、記録から自動的に削除されることはありません。\n\nここでは保留中の各通知を手動で送信または削除できます。
ダウンロードしました
削除しました
- 有効化済み
- 無効化済み
+ 有効化しました
+ 無効化しました
処理
削除
eUICC 情報
@@ -135,33 +110,20 @@
製品ファームウェアバージョン
SGP.22 バージョン
eUICC OS バージョン
- グローバルプラットフォームのバージョン
+ グローバルプラットフォームのバージョン
SAS 認定番号
保護されたプロファイルのバージョン
NVRAM の空き容量 (eSIM プロファイルストレージ)
- (参照用)
証明書発行者 (CI)
GSMA ライブ CI
GSMA テスト CI
不明な eSIM CI
- eUICC を消去
- eUICC を消去
- このチップ上のすべてのプロファイルを削除することを確認してください。この操作は元に戻せないことを理解してください。\n\nEID: %1$s\n\n%2$s
- 確認のために「%s」を入力してください
- EID が %s で終わるチップを消去することを確認し、この操作は元に戻せないことを理解してください
- 消去
-
- はい
- いいえ
- 不明
- 情報がありません
+ はい
+ いいえ
保存
%s のログ
開発者になるまであと %d ステップです。
あなたは開発者になりました!
- ISD-R AID リスト
- カスタム ISD-R AID リストを保存しました。
- リセット
設定
通知
eSIM のプロファイル操作により、通信事業者に通知が送信されます。必要に応じてこの動作を微調整できます。
@@ -173,7 +135,7 @@
プロファイルを切り替え中の通知を送信します\nこのタイプの通知は信頼できないことに注意してください。
高度な設定
プロファイルの無効化と削除を許可
- デフォルトでは、このアプリでデバイスに挿入されたリムーバブル eSIM の有効なプロファイルを無効化することを防いでいます。なぜなのかというと時々アクセスができなくなるからです。\nこのチェックボックスを ON にすることで、この保護機能を解除します。
+ デフォルトでは、このアプリでデバイスに挿入された取り外し可能な eSIM の有効なプロファイルを無効化することを防いでいます。なぜなのかというと時々アクセスができなくなるからです。\nこのチェックボックスを ON にすることで、この保護機能を解除します。
詳細ログ
詳細ログを有効化します。これには個人的な情報が含まれている可能性があります。この機能を ON にした後は、信頼できるユーザーとのみログを共有してください。
言語
@@ -181,22 +143,23 @@
ログ
アプリの最新デバッグログを表示します
開発者オプション
- モデムに更新コマンドを送信
- プロファイルを切り替えた後にモデムに更新コマンドを送信するかどうかを設定します。クラッシュが発生する場合は、この機能を無効化してください。
フィルタリングされていないプロファイル一覧を表示
非運用のプロファイルも含めます
SM-DP+ TLS 証明書を無視する
RSP サーバーで使用される TLS 証明書を受け入れます
- eUICC の消去を許可
- これは危険な操作であり、デフォルトでは非表示になっています。代わりとしてすべてのプロファイルを手動で削除することもできます。
- グローバル ES10x MSS
-
- - 高速
- - 互換モード
-
- ISD-R AID リストをカスタマイズ
- 一部ブランドのリムーバブル eUICC は独自の非標準な ISD-R AID を使用しているため、サードパーティー製アプリからアクセスできない場合があります。このリストに追加された非標準な AID の使用を試みますが、動作の保証はできません。
情報
アプリバージョン
ソースコード
+ 確認文字列が一致しません
+ このチップは消去されました
+ eSIM チップを消去しています
+ eSIM チップの消去は失敗しました
+ eSIM を消去する
+ eSIM を消去する
+ このチップ内のすべてのプロファイルを削除することをご確認してください。この操作は元に戻せないことをご理解してください。\n\nEID: %s\n\n%s
+ 確認のため、ここに「%s」を入力してください
+ EID が %s で終わるチップを消去することに同意します。これは元に戻せないことを理解しています。
+ 消去する
+ eUICC の消去を可能にする
+ この操作は、デフォルトでは非表示になっている危険な操作です。代わりに、すべての構成ファイルを手動で削除することもできます。
diff --git a/app-common/src/main/res/values-zh-rCN/strings.xml b/app-common/src/main/res/values-zh-rCN/strings.xml
index 7303d2d..8c36091 100644
--- a/app-common/src/main/res/values-zh-rCN/strings.xml
+++ b/app-common/src/main/res/values-zh-rCN/strings.xml
@@ -2,21 +2,20 @@
在此设备上未检测到此应用程序可访问的可插拔 eUICC 卡。请插入兼容卡或 USB 读卡器。
此 eSIM 上还没有配置文件
- 未知
- 帮助
- 重新加载卡槽
- 未知
+ 未知
+ 帮助
+ 重新加载卡槽
逻辑卡槽 %d
- 已启用
- 已禁用
- 提供商:
+ 已启用
+ 已禁用
+ 提供商:
类型:
- 启用
- 禁用
- 删除
- 重命名
- 等待 eSIM 芯片切换配置文件时超时。这可能是您手机基带固件中的一个错误。请尝试切换飞行模式、重新启动应用程序或重新启动手机
- 操作成功, 但是您手机的基带拒绝刷新。您可能需要切换飞行模式或重新启动,以便使用新的配置文件。
+ 启用
+ 禁用
+ 删除
+ 重命名
+ 等待 eSIM 芯片切换配置文件时超时。这可能是您手机基带固件中的一个错误。请尝试切换飞行模式、重新启动应用程序或重新启动手机
+ 操作成功, 但是您手机的基带拒绝刷新。您可能需要切换飞行模式或重新启动,以便使用新的配置文件。
无法切换到新的 eSIM 配置文件。
输入的确认文本不匹配
已复制 ICCID 到剪贴板
@@ -47,7 +46,6 @@
IMEI (可选)
剩余空间不足
当前芯片的剩余空间不足,可能导致配置下载失败。\n是否继续下载?
- 请连接到其他网络(例如在 Wi-Fi 和数据之间切换)后重试。
日志已保存到指定路径。需要通过其他 App 分享吗?
新昵称
无法将昵称编码为 UTF-8
@@ -67,7 +65,6 @@
删除
保存日志
%s 的日志
- 自定义 ISD-R AID 列表已保存
设置
通知
操作 eSIM 配置文件会向运营商发送通知。根据需要在此处微调此行为。
@@ -84,12 +81,6 @@
详细日志中包含敏感信息,开启此功能后请仅与你信任的人共享你的日志。
日志
查看应用程序的最新调试日志
- 某些品牌的可移除 eUICC 可能会使用自己的非标准 ISD-R AID,导致第三方应用无法访问。此 App 可以尝试使用此列表中添加的非标准 AID,但不能保证它们一定有效。
- 全局 ES10x MSS
-
- - 最佳效率
- - 最佳兼容性
-
信息
App 版本
源码
@@ -139,62 +130,36 @@
可插拔
SGP.22 版本
eUICC OS 版本
- GlobalPlatform 版本
+ GlobalPlatform 版本
SAS 认证号码
Protected Profile 版本
NVRAM 剩余空间 (eSIM 存储容量)
- (仅供参考)
证书签发者 (CI)
GSMA 生产环境 CI
GSMA 测试 CI
未知 eSIM CI
- 是
- 否
+ 是
+ 否
还有 %d 步成为开发者
你现在是开发者了!
语言
选择 App 语言
开发者选项
- 切换配置文件后是否向基带发送刷新命令。如果发现崩溃,请尝试禁用此功能。
显示未经过滤的配置文件列表
在配置文件列表中包括非生产环境的配置文件
无视 SM-DP+ 的 TLS 证书
允许 RSP 服务器使用任意证书
- 无信息
+ 无信息
输入的确认文本不匹配
此芯片已被擦除
正在擦除 eSIM 芯片
eSIM 芯片擦除失败
擦除 eSIM 芯片
擦除 eSIM 芯片
- 请确认删除此芯片上的所有配置文件,并了解此操作不可逆。\n\nEID: %1$s\n\n%2$s
+ 请确认删除此芯片上的所有配置文件,并了解此操作不可逆。\n\nEID: %s\n\n%s
请在此处输入「%s」以确认
我确认擦除 EID 以 %s 结尾的芯片,并了解此操作不可逆
擦除
允许擦除 eUICC
此操作是默认隐藏的危险操作。作为替代方案,您可以手动删除所有配置文件。
- 向基带发送刷新命令
- 自定义 ISD-R AID 列表
- 重置
- ISD-R AID 列表
- 此 eSIM 配置文件已存在于您的 eSIM 芯片上。
- 您的 eSIM 芯片没有足够的空间来下载配置文件。
- 您的 eSIM 芯片不支持此 eSIM 配置文件。
- eSIM 芯片错误。
- 您的设备或 eSIM 芯片的 EID 不受您的运营商支持。
- 此 eSIM 配置文件已被下载到另一台设备上。
- 此 eSIM 配置文件已被撤销。
- 激活码无效。
- 已超出 eSIM 配置文件的最大下载尝试次数。
- 下载此配置文件需要确认码。
- 您输入的确认码无效。
- 此 eSIM 配置文件已过期。
- 已超出确认码的最大下载尝试次数。
- 未知的 SM-DP+ 地址
- 网络不可达
- TLS 证书错误,不支持此 eSIM 配置文件
- 您正在尝试重新安装已下载的 eSIM 配置文件
- 请删除一些未使用的 eSIM 配置文件,然后重试
- 请联系您的运营商寻求帮助。
- 请联系您的运营商重新签发此 eSIM 配置文件。
\ No newline at end of file
diff --git a/app-common/src/main/res/values-zh-rTW/strings.xml b/app-common/src/main/res/values-zh-rTW/strings.xml
index ef6c842..3d8270d 100644
--- a/app-common/src/main/res/values-zh-rTW/strings.xml
+++ b/app-common/src/main/res/values-zh-rTW/strings.xml
@@ -2,21 +2,20 @@
在此裝置上未檢測到此應用程式可訪問的可插拔 eUICC 卡。請插入相容卡或 USB 晶片讀卡機。
此 eSIM 上還沒有設定檔
- 未知
- 幫助
- 重新載入卡槽
- 未知
+ 未知
+ 幫助
+ 重新載入卡槽
虛擬卡槽 %d
- 已啟用
- 已停用
- 電信業者:
+ 已啟用
+ 已停用
+ 電信業者:
類型:
- 啟用
- 停用
- 刪除
- 重新命名
- 等待 eSIM 切換設定檔時逾時。這可能是您手機基頻處理器韌體中的一個錯誤。請嘗試切換飛航模式、重新啟動應用程式或重新啟動手機
- 操作成功, 但是您手機的基頻處理器沒有重新整理。您可能需要切換飛航模式或重新啟動,以便使用新的設定檔。
+ 啟用
+ 停用
+ 刪除
+ 重新命名
+ 等待 eSIM 切換設定檔時逾時。這可能是您手機基頻處理器韌體中的一個錯誤。請嘗試切換飛航模式、重新啟動應用程式或重新啟動手機
+ 操作成功, 但是您手機的基頻處理器沒有重新整理。您可能需要切換飛航模式或重新啟動,以便使用新的設定檔。
無法切換到新的 eSIM 設定檔。
輸入的確認文字不匹配
已複製 ICCID 到剪貼簿
@@ -47,7 +46,6 @@
IMEI (可選)
剩餘空間不足
目前晶片的剩餘空間不足,可能導致配置下載失敗。\n是否繼續下載?
- 請連接到其他網路(例如在 Wi-Fi 和資料之間切換)後重試。
日誌已儲存到指定路徑。需要透過其他 App 分享嗎?
新名稱
無法將名稱編碼為 UTF-8
@@ -67,7 +65,6 @@
刪除
儲存日誌
%s 的日誌
- 自訂 ISD-R AID 列表已儲存
設定
通知
變更 eSIM 設定檔會向電信業者傳送通知。根據需要在此處微調此行為。
@@ -84,8 +81,6 @@
進階
允許 停用/刪除 已啟用的設定檔
預設情況下,此應用程式會阻止您停用可插拔 eSIM 中已啟用的設定檔。\n因為這樣做 有時 會導致無法存取。\n勾選此框以 移除 此保護措施。
- 某些品牌的可移除 eUICC 可能會使用自己的非標準 ISD-R AID,導致第三方應用程式無法存取。此 App 可以嘗試使用此清單中新增的非標準 AID,但不能保證它們一定有效。
- 全局 ES10x MSS
資訊
App 版本
原始碼
@@ -135,62 +130,36 @@
可插拔
SGP.22 版本
eUICC OS 版本
- GlobalPlatform 版本
+ GlobalPlatform 版本
SAS 認證號碼
Protected Profile 版本
NVRAM 剩餘空間 (eSIM 儲存容量)
- (僅供參考)
證書簽發者 (CI)
GSMA 生產環境 CI
GSMA 測試 CI
未知 eSIM CI
- 是
- 否
+ 是
+ 否
還有 %d 步成為開發者
您現在是開發者了!
語言
選擇 App 語言
開發人員選項
- 切換設定檔後是否向基帶發送刷新命令。如果發現崩潰,請嘗試停用此功能。
顯示未經過濾的設定檔列表
在設定檔列表中包括非生產環境的設定檔
忽略 SM-DP+ 的 TLS 證書
允許 RSP 伺服器使用任意證書
- 無資訊
+ 無資訊
輸入的確認文字不匹配
此晶片已被擦除
正在擦除 eSIM 晶片
eSIM 晶片擦除失敗
擦除 eSIM 晶片
擦除 eSIM 晶片
- 請確認刪除此晶片上的所有配置文件,並了解此操作不可逆。\n\nEID: %1$s\n\n%2$s
+ 請確認刪除此晶片上的所有配置文件,並了解此操作不可逆。\n\nEID: %s\n\n%s
請在此輸入「%s」以確認
我確認擦除 EID 以 %s 結尾的晶片,並了解此操作不可逆
擦除
允許擦除 eUICC
此操作是預設隱藏的危險操作。作為替代方案,您可以手動刪除所有設定檔。
- 向基帶發送刷新命令
- 自訂 ISD-R AID 列表
- 重置
- ISD-R AID 列表
- 此 eSIM 設定檔已存在於您的 eSIM 晶片上。
- 您的 eSIM 晶片沒有足夠的空間來下載設定檔。
- 您的 eSIM 晶片不支援此 eSIM 設定檔。
- eSIM 晶片錯誤。
- 您的裝置或 eSIM 晶片的 EID 不受您的電信業者支援。
- 此 eSIM 設定檔已被下載到另一台裝置上。
- 此 eSIM 設定檔已被撤銷。
- 啟用碼無效。
- 已超出 eSIM 設定檔的最大下載嘗試次數。
- 下載此設定檔需要確認碼。
- 您輸入的確認碼無效。
- 此 eSIM 設定檔已過期。
- 已超出確認碼的最大下載嘗試次數。
- 未知的 SM-DP+ 位址
- 網路不可達
- TLS 憑證錯誤,不支援此 eSIM 設定檔
- 您正在嘗試重新安裝已下載的 eSIM 設定文件
- 請刪除一些未使用的 eSIM 設定文件,然後重試
- 請聯絡您的電信業者尋求協助。
- 請聯絡您的電信業者重新簽發此 eSIM 設定檔。
\ No newline at end of file
diff --git a/app-common/src/main/res/values/strings.xml b/app-common/src/main/res/values/strings.xml
index 39a762f..cc84381 100644
--- a/app-common/src/main/res/values/strings.xml
+++ b/app-common/src/main/res/values/strings.xml
@@ -2,34 +2,31 @@
No removable eUICC card accessible by this app is detected on this device. Insert a compatible card or a USB reader.
No profiles (yet) on this eSIM.
-
- Help
-
- Reload Slots
- Unknown
+ Unknown
+ Information Unavailable
+ Help
+ Reload Slots
Logical Slot %d
- USB
- OpenMobile API (OMAPI)
+ USB
+ OpenMobile API (OMAPI)
-
- Enabled
- Disabled
- Provider:
+ Enabled
+ Disabled
+ Provider:
Class:
Testing
Provisioning
Operational
- ICCID:
- #%d
+ ICCID:
- Enable
- Disable
- Delete
- Rename
+ Enable
+ Disable
+ Delete
+ Rename
- Timed out waiting for the eSIM chip to switch profiles. This may be a bug in your phone\'s modem firmware. Try toggling airplane mode, restarting the application, or rebooting the phone.
- The operation was successful, but your phone\'s modem refused to refresh. You might need to toggle airplane mode or reboot in order to use the new profile.
+ Timed out waiting for the eSIM chip to switch profiles. This may be a bug in your phone\'s modem firmware. Try toggling airplane mode, restarting the application, or rebooting the phone.
+ The operation was successful, but your phone\'s modem refused to refresh. You might need to toggle airplane mode or reboot in order to use the new profile.
Cannot switch to new eSIM profile.
Confirmation string mismatch
@@ -104,27 +101,6 @@
Last APDU exception:
Save
Diagnostics at %s
- This eSIM profile is already present on your eSIM chip.
- Your eSIM chip does not have sufficient memory left to download the profile.
- This eSIM profile is unsupported by your eSIM chip.
- An error occurred in your eSIM chip.
- The EID of your device or eSIM chip is unsupported by your carrier.
- This eSIM profile has been downloaded on another device.
- This eSIM profile has been revoked.
- The activation code is invalid.
- The maximum number of download attempts for the eSIM profile has been exceeded.
- Confirmation code is required to download this profile.
- The confirmation code you entered is invalid.
- This eSIM profile has expired.
- The maximum number of download attempts for the confirmation code has been exceeded.
- Unknown SM-DP+ address
- Network is unreachable
- TLS certificate error, this eSIM profile is not supported
- You are trying to reinstall an already downloaded eSIM profile
- Please delete some unused eSIM profiles and try again
- Please contact your carrier for assistance.
- Please contact your carrier to reissue this eSIM profile.
- Please try again after connecting to a different network (e.g. switching between Wi-Fi and data).
Logs have been saved to the selected path. Would you like to share the log through another app?
@@ -158,14 +134,12 @@
Product Bootloader Version
Product Firmware Version
EID
- ISD-R AID
SGP.22 Version
eUICC OS Version
- GlobalPlatform Version
+ GlobalPlatform Version
SAS Accreditation Number
Protected Profile Version
Free NVRAM (eSIM profile storage)
- (for reference only)
Certificate Issuer (CI)
GSMA Live CI
GSMA Test CI
@@ -174,16 +148,13 @@
Erase eUICC
Erase eUICC
- Please confirm to delete all profiles on this chip and understand that this operation is irreversible.\n\nEID: %1$s\n\n%2$s
+ Please confirm to delete all profiles on this chip and understand that this operation is irreversible.\n\nEID: %s\n\n%s
Type \'%s\' here to confirm
I CONFIRM TO ERASE THE CHIP WHOSE EID ENDS WITH %s AND UNDERSTAND THAT THIS IS IRREVERSIBLE
Erase
-
- Yes
- No
- Unknown
- Information Unavailable
+ Yes
+ No
Save
Logs at %s
@@ -191,10 +162,6 @@
You are %d steps away from being a developer.
You are now a developer!
- ISD-R AID List
- Saved custom ISD-R AID list.
- Reset
-
Settings
Notifications
eSIM profile operations send notifications to the carrier. Fine-tune this behavior as needed here.
@@ -214,26 +181,12 @@
Logs
View recent debug logs of the application
Developer Options
- Send refresh command to modem
- Whether to send a refresh command to the modem after switching profiles. Try disabling this if you see crashes.
Show unfiltered profile list
Include non-production profiles in the list
Ignore SM-DP+ TLS certificate
Accept any TLS certificate used by the RSP server
Allow erasing eUICC
This is a dangerous operation and hidden by default. As an alternative, you can delete all profiles manually.
- ES10x MSS
- Global ES10x MSS
-
- - High Efficiency
- - Most Compatible
-
-
- - 255
- - 63
-
- Customize ISD-R AID list
- Some brands of removable eUICCs may use their own non-standard ISD-R AID, rendering them inaccessible to third-party apps. We can attempt to use non-standard AIDs added in this list, but there is no guarantee that they will work.
Info
App Version
Source Code
diff --git a/app-common/src/main/res/xml/pref_settings.xml b/app-common/src/main/res/xml/pref_settings.xml
index 831b04d..7d25118 100644
--- a/app-common/src/main/res/xml/pref_settings.xml
+++ b/app-common/src/main/res/xml/pref_settings.xml
@@ -57,12 +57,6 @@
app:title="@string/pref_developer"
app:iconSpaceReserved="false">
-
-
-
-
-
-
+ android:label="@string/compatibility_check" />
by lazy { getCompatibilityChecks(this) }
+ private val adapter = CompatibilityChecksAdapter()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ enableEdgeToEdge()
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_compatibility_check)
+ setSupportActionBar(requireViewById(im.angry.openeuicc.common.R.id.toolbar))
+ setupToolbarInsets()
+ supportActionBar!!.setDisplayHomeAsUpEnabled(true)
+
+ compatibilityCheckList = requireViewById(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 {
+ compatibilityChecks.executeAll { adapter.notifyDataSetChanged() }
+ }
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean =
+ when (item.itemId) {
+ android.R.id.home -> {
+ finish()
+ true
+ }
+ else -> super.onOptionsItemSelected(item)
+ }
+
+ inner class ViewHolder(private val root: View): RecyclerView.ViewHolder(root) {
+ private val titleView: TextView = root.requireViewById(R.id.compatibility_check_title)
+ private val descView: TextView = root.requireViewById(R.id.compatibility_check_desc)
+ private val statusContainer: ViewGroup = root.requireViewById(R.id.compatibility_check_status_container)
+
+ fun bindItem(item: CompatibilityCheck) {
+ titleView.text = item.title
+ descView.text = Html.fromHtml(item.description, Html.FROM_HTML_MODE_COMPACT)
+
+ statusContainer.children.forEach {
+ it.isVisible = false
+ }
+
+ val viewId = when (item.state) {
+ CompatibilityCheck.State.SUCCESS -> R.id.compatibility_check_checkmark
+ CompatibilityCheck.State.FAILURE -> R.id.compatibility_check_error
+ CompatibilityCheck.State.FAILURE_UNKNOWN -> R.id.compatibility_check_unknown
+ else -> R.id.compatibility_check_progress_bar
+ }
+ root.requireViewById(viewId).isVisible = true
+ }
+ }
+
+ inner class CompatibilityChecksAdapter: RecyclerView.Adapter() {
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
+ ViewHolder(layoutInflater.inflate(R.layout.compatibility_check_item, parent, false))
+
+ override fun getItemCount(): Int =
+ compatibilityChecks.indexOfLast { it.state != CompatibilityCheck.State.NOT_STARTED } + 1
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ holder.bindItem(compatibilityChecks[position])
+ }
+ }
+}
\ No newline at end of file
diff --git a/app-unpriv/src/main/java/im/angry/openeuicc/ui/QuickCompatibilityActivity.kt b/app-unpriv/src/main/java/im/angry/openeuicc/ui/QuickCompatibilityActivity.kt
deleted file mode 100644
index d5e599f..0000000
--- a/app-unpriv/src/main/java/im/angry/openeuicc/ui/QuickCompatibilityActivity.kt
+++ /dev/null
@@ -1,24 +0,0 @@
-package im.angry.openeuicc.ui
-
-import android.os.Bundle
-import androidx.activity.enableEdgeToEdge
-import androidx.appcompat.app.AppCompatActivity
-import im.angry.easyeuicc.R
-import im.angry.openeuicc.di.UnprivilegedUiComponentFactory
-import im.angry.openeuicc.util.OpenEuiccContextMarker
-
-class QuickCompatibilityActivity : AppCompatActivity(), OpenEuiccContextMarker {
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- enableEdgeToEdge()
- setContentView(R.layout.activity_quick_compatibility)
-
- val quickCompatibilityFragment =
- (appContainer.uiComponentFactory as UnprivilegedUiComponentFactory)
- .createQuickCompatibilityFragment()
-
- supportFragmentManager.beginTransaction()
- .replace(R.id.quick_compatibility_container, quickCompatibilityFragment)
- .commit()
- }
-}
diff --git a/app-unpriv/src/main/java/im/angry/openeuicc/ui/QuickCompatibilityFragment.kt b/app-unpriv/src/main/java/im/angry/openeuicc/ui/QuickCompatibilityFragment.kt
deleted file mode 100644
index 9b41730..0000000
--- a/app-unpriv/src/main/java/im/angry/openeuicc/ui/QuickCompatibilityFragment.kt
+++ /dev/null
@@ -1,186 +0,0 @@
-package im.angry.openeuicc.ui
-
-import android.content.pm.PackageManager
-import android.icu.text.ListFormatter
-import android.os.Build
-import android.os.Bundle
-import android.se.omapi.Reader
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.Button
-import android.widget.CheckBox
-import android.widget.TextView
-import androidx.core.view.isVisible
-import androidx.fragment.app.Fragment
-import androidx.lifecycle.lifecycleScope
-import im.angry.easyeuicc.R
-import im.angry.openeuicc.util.EUICC_DEFAULT_ISDR_AID
-import im.angry.openeuicc.util.UnprivilegedEuiccContextMarker
-import im.angry.openeuicc.util.connectSEService
-import im.angry.openeuicc.util.decodeHex
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.withContext
-
-open class QuickCompatibilityFragment : Fragment(), UnprivilegedEuiccContextMarker {
- companion object {
- enum class Compatibility {
- COMPATIBLE,
- NOT_COMPATIBLE,
- }
-
- data class CompatibilityResult(
- val compatibility: Compatibility,
- val slotsOmapi: List = emptyList(),
- val slotsIsdr: List = emptyList()
- )
- }
-
- private val conclusion: TextView by lazy {
- requireView().requireViewById(R.id.quick_compatibility_conclusion)
- }
-
- private val resultSlots: TextView by lazy {
- requireView().requireViewById(R.id.quick_compatibility_result_slots)
- }
-
- private val resultSlotsIsdr: TextView by lazy {
- requireView().requireViewById(R.id.quick_compatibility_result_slots_isdr)
- }
-
- private val resultNotes: TextView by lazy {
- requireView().requireViewById(R.id.quick_compatibility_result_notes)
- }
-
- private val skipCheckBox: CheckBox by lazy {
- requireView().requireViewById(R.id.quick_compatibility_skip)
- }
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View = inflater.inflate(R.layout.fragment_quick_compatibility, container, false).apply {
- requireViewById(R.id.quick_compatibility_device_information)
- .text = formatDeviceInformation()
- requireViewById
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 4012227..b9c2100 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,4 +1,3 @@
-import com.android.build.gradle.internal.api.ApkVariantOutputImpl
import im.angry.openeuicc.build.*
plugins {
@@ -24,9 +23,6 @@ android {
}
buildTypes {
- defaultConfig {
- versionNameSuffix = "-priv"
- }
release {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
@@ -49,63 +45,4 @@ dependencies {
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
-}
-
-val modulePropsTemplate = mutableMapOf(
- "id" to android.defaultConfig.applicationId!!,
- "name" to "OpenEUICC",
- "version" to android.defaultConfig.versionName!!,
- "versionCode" to "${android.defaultConfig.versionCode}",
- "author" to "OpenEUICC authors",
- "description" to "OpenEUICC is an open-source app that provides system-level eSIM integration."
-)
-
-val moduleCustomizeScript = project.file("magisk/customize.sh").readText()
- .replace("{APK_NAME}", "OpenEUICC")
- .replace("{PKG_NAME}", android.defaultConfig.applicationId!!)
-
-val moduleUninstallScript = project.file("magisk/uninstall.sh").readText()
- .replace("{PKG_NAME}", android.defaultConfig.applicationId!!)
-
-tasks.register("assembleDebugMagiskModuleDir") {
- variant = "debug"
- appName = "OpenEUICC"
- permsFile = project.rootProject.file("privapp_whitelist_im.angry.openeuicc.xml")
- moduleInstaller = project.file("magisk/module_installer.sh")
- moduleCustomizeScriptText = moduleCustomizeScript
- moduleUninstallScriptText = moduleUninstallScript
- moduleProp = modulePropsTemplate.let {
- it["description"] = "(debug build) ${it["description"]}"
- it["versionCode"] = "${(android.applicationVariants.find { it.name == "debug" }!!.outputs.first() as ApkVariantOutputImpl).versionCodeOverride}"
- it["updateJson"] = "https://openeuicc.com/magisk/magisk-debug.json"
- it
- }
- dependsOn("assembleDebug")
-}
-
-tasks.register("assembleDebugMagiskModule") {
- dependsOn("assembleDebugMagiskModuleDir")
- from((tasks.getByName("assembleDebugMagiskModuleDir") as MagiskModuleDirTask).outputDir)
- archiveFileName = "magisk-debug.zip"
- destinationDirectory = project.layout.buildDirectory.dir("magisk")
- entryCompression = ZipEntryCompression.STORED
-}
-
-tasks.register("assembleReleaseMagiskModuleDir") {
- variant = "release"
- appName = "OpenEUICC"
- permsFile = project.rootProject.file("privapp_whitelist_im.angry.openeuicc.xml")
- moduleInstaller = project.file("magisk/module_installer.sh")
- moduleCustomizeScriptText = moduleCustomizeScript
- moduleUninstallScriptText = moduleUninstallScript
- moduleProp = modulePropsTemplate
- dependsOn("assembleRelease")
-}
-
-tasks.register("assembleReleaseMagiskModule") {
- dependsOn("assembleReleaseMagiskModuleDir")
- from((tasks.getByName("assembleReleaseMagiskModuleDir") as MagiskModuleDirTask).outputDir)
- archiveFileName = "magisk-release.zip"
- destinationDirectory = project.layout.buildDirectory.dir("magisk")
- entryCompression = ZipEntryCompression.STORED
}
\ No newline at end of file
diff --git a/app/magisk/customize.sh b/app/magisk/customize.sh
deleted file mode 100644
index 3b57a55..0000000
--- a/app/magisk/customize.sh
+++ /dev/null
@@ -1,9 +0,0 @@
-TMP_FILE="$TMPDIR/{APK_NAME}"
-
-chmod u+x "$MODPATH/uninstall.sh"
-cp "$MODPATH/system/system_ext/priv-app/{APK_NAME}/{APK_NAME}.apk" "$TMP_FILE"
-
-pm install -r "$TMP_FILE"
-rm -f "$TMP_FILE"
-
-pm grant "{PKG_NAME}" android.permission.READ_PHONE_STATE
\ No newline at end of file
diff --git a/app/magisk/module_installer.sh b/app/magisk/module_installer.sh
deleted file mode 100644
index 28b48e5..0000000
--- a/app/magisk/module_installer.sh
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/sbin/sh
-
-#################
-# Initialization
-#################
-
-umask 022
-
-# echo before loading util_functions
-ui_print() { echo "$1"; }
-
-require_new_magisk() {
- ui_print "*******************************"
- ui_print " Please install Magisk v20.4+! "
- ui_print "*******************************"
- exit 1
-}
-
-#########################
-# Load util_functions.sh
-#########################
-
-OUTFD=$2
-ZIPFILE=$3
-
-mount /data 2>/dev/null
-
-[ -f /data/adb/magisk/util_functions.sh ] || require_new_magisk
-. /data/adb/magisk/util_functions.sh
-[ $MAGISK_VER_CODE -lt 20400 ] && require_new_magisk
-
-install_module
-exit 0
diff --git a/app/magisk/uninstall.sh b/app/magisk/uninstall.sh
deleted file mode 100644
index 1eb0200..0000000
--- a/app/magisk/uninstall.sh
+++ /dev/null
@@ -1 +0,0 @@
-pm uninstall "{PKG_NAME}"
\ No newline at end of file
diff --git a/app/src/main/java/im/angry/openeuicc/core/PrivilegedEuiccChannelFactory.kt b/app/src/main/java/im/angry/openeuicc/core/PrivilegedEuiccChannelFactory.kt
index 876387f..6dccda9 100644
--- a/app/src/main/java/im/angry/openeuicc/core/PrivilegedEuiccChannelFactory.kt
+++ b/app/src/main/java/im/angry/openeuicc/core/PrivilegedEuiccChannelFactory.kt
@@ -2,6 +2,7 @@ package im.angry.openeuicc.core
import android.content.Context
import android.util.Log
+import im.angry.openeuicc.OpenEuiccApplication
import im.angry.openeuicc.R
import im.angry.openeuicc.util.*
import kotlinx.coroutines.flow.first
@@ -9,19 +10,17 @@ import java.lang.IllegalArgumentException
class PrivilegedEuiccChannelFactory(context: Context) : DefaultEuiccChannelFactory(context),
PrivilegedEuiccContextMarker {
- override val openEuiccMarkerContext: Context
- get() = context
+ private val tm by lazy {
+ (context.applicationContext as OpenEuiccApplication).appContainer.telephonyManager
+ }
@Suppress("NAME_SHADOWING")
- override suspend fun tryOpenEuiccChannel(
- port: UiccPortInfoCompat,
- isdrAid: ByteArray
- ): EuiccChannel? {
+ override suspend fun tryOpenEuiccChannel(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
- super.tryOpenEuiccChannel(port, isdrAid)?.let { return it }
+ super.tryOpenEuiccChannel(port)?.let { return it }
}
if (port.card.isEuicc || preferenceRepository.removableTelephonyManagerFlow.first()) {
@@ -31,28 +30,26 @@ class PrivilegedEuiccChannelFactory(context: Context) : DefaultEuiccChannelFacto
)
try {
return EuiccChannelImpl(
- context.getString(R.string.channel_type_telephony_manager),
+ context.getString(R.string.telephony_manager),
port,
intrinsicChannelName = null,
TelephonyManagerApduInterface(
port,
- telephonyManager,
+ tm,
context.preferenceRepository.verboseLoggingFlow
),
- isdrAid,
context.preferenceRepository.verboseLoggingFlow,
context.preferenceRepository.ignoreTLSCertificateFlow,
- context.preferenceRepository.es10xMssFlow,
)
- } catch (_: IllegalArgumentException) {
+ } catch (e: IllegalArgumentException) {
// Failed
Log.w(
DefaultEuiccChannelManager.TAG,
- "TelephonyManager APDU interface unavailable for slot ${port.card.physicalSlotIndex} port ${port.portIndex} with ISD-R AID: ${isdrAid.encodeHex()}."
+ "TelephonyManager APDU interface unavailable for slot ${port.card.physicalSlotIndex} port ${port.portIndex}, falling back"
)
}
}
- return super.tryOpenEuiccChannel(port, isdrAid)
+ return super.tryOpenEuiccChannel(port)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/im/angry/openeuicc/util/PrivilegedUtils.kt b/app/src/main/java/im/angry/openeuicc/util/PrivilegedUtils.kt
index 6418581..21c8002 100644
--- a/app/src/main/java/im/angry/openeuicc/util/PrivilegedUtils.kt
+++ b/app/src/main/java/im/angry/openeuicc/util/PrivilegedUtils.kt
@@ -10,9 +10,16 @@ import java.util.concurrent.Executors
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
-interface PrivilegedEuiccContextMarker : OpenEuiccContextMarker {
- override val preferenceRepository: PrivilegedPreferenceRepository
- get() = appContainer.preferenceRepository as PrivilegedPreferenceRepository
+interface PrivilegedEuiccContextMarker {
+ val privilegedEuiccMarkerContext: Context
+ get() = when (this) {
+ is Context -> this
+ is Fragment -> requireContext()
+ else -> throw RuntimeException("PrivilegedEuiccContextMarker shall only be used on Fragments or UI types that derive from Context")
+ }
+
+ val preferenceRepository: PrivilegedPreferenceRepository
+ get() = privilegedEuiccMarkerContext.preferenceRepository as PrivilegedPreferenceRepository
}
suspend fun Context.bindServiceSuspended(intent: Intent, flags: Int): Pair Unit> =
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index 920801c..acc1728 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -1,7 +1,7 @@
このデバイスで eUICC が見つかりません。\nデバイスによってはアプリのメニューからデュアル SIM を有効化する必要があります。
- TelephonyManager (特権)
+ TelephonyManager (特権)
デュアル SIM
DSDS の状態が切り替わりました。モデムが再起動するまでお待ちください。
このスロットは MEP (Multiple Enabled Profiles) をサポートしています。この機能を有効化または無効化するには「スロットマッピングツール」を使用してください。
@@ -17,7 +17,6 @@
使用しているデバイスは eSIM をサポートしています。モバイルネットワークに接続するには通信事業者が発行した eSIM をダウンロードするか、物理 SIM を挿入してください。
スキップ
eSIM をダウンロード
-
- TelephonyManager を常に使用可能にする
- デフォルトでは、非特権モード (EasyEUICC など) での動作と一致させるためにリムーバブル eUICC に対しては OMAPI のみを試行します。ただし、一部デバイスでは正常に動作しない場合があります。このオプションを選択するとリムーバブル eUICC でも TelephonyManager の使用が強制されます。
+ TelephonyManagerをどこでも使用
+ デフォルトでは、非特権モード (EasyEUICC) と一致するように、取り外し可能な eUICC に対して OMAPI のみが試行されます。これは、一部のデバイスではうまく機能しない可能性があります。このオプションを選択する場合、取り外し可能な eUICC でも TelephonyManager を使用することになります。
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index acd9a61..d6befc2 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -16,7 +16,7 @@
您的设备支持 eSIM。要连接到移动网络,请下载运营商发布的 eSIM,或插入物理 SIM 卡。
跳过
下载 eSIM
- TelephonyManager (特权)
+ TelephonyManager (特权)
全局使用 TelephonyManager
在默认情况下,可移除 eUICC 将仅使用 OMAPI。这与非特权模式 (EasyEUICC) 一致。在某些设备上 OMAPI 可能存在问题 -- 选择此选项以强制使用 TelephonyManager。
\ No newline at end of file
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index 52b5aa8..10285d3 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -16,7 +16,7 @@
您的裝置支援 eSIM。要連線到行動網路,請下載電信業者釋出的 eSIM,或插入實體 SIM 卡。
跳過
下載 eSIM
- TelephonyManager (特權)
+ TelephonyManager (特權)
全域使用 TelephonyManager
在預設情況下,可移除 eUICC 將僅使用 OMAPI。這與非特權模式 (EasyEUICC) 一致。在某些裝置上 OMAPI 可能有問題 -- 選擇此選項以強制使用 TelephonyManager。
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 2c26bc3..ddf17e4 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,7 +1,7 @@
OpenEUICC
No eUICC found on this device.\nOn some devices, you may need to enable dual SIM first in the menu of this app.
- TelephonyManager (Privileged)
+ TelephonyManager (Privileged)
Dual SIM
diff --git a/buildSrc/src/main/kotlin/im/angry/openeuicc/build/Magisk.kt b/buildSrc/src/main/kotlin/im/angry/openeuicc/build/Magisk.kt
deleted file mode 100644
index e5ccfa3..0000000
--- a/buildSrc/src/main/kotlin/im/angry/openeuicc/build/Magisk.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-package im.angry.openeuicc.build
-
-import org.gradle.api.DefaultTask
-import org.gradle.api.provider.MapProperty
-import org.gradle.api.provider.Property
-import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.InputDirectory
-import org.gradle.api.tasks.InputFile
-import org.gradle.api.tasks.OutputDirectory
-import org.gradle.api.tasks.TaskAction
-import java.io.File
-
-abstract class MagiskModuleDirTask : DefaultTask() {
- @get:Input
- abstract val variant : Property
-
- @get:Input
- abstract val appName : Property
-
- @get:InputFile
- abstract val permsFile : Property
-
- @get:InputFile
- abstract val moduleInstaller : Property
-
- @get:Input
- abstract val moduleCustomizeScriptText : Property
-
- @get:Input
- abstract val moduleUninstallScriptText : Property
-
- @get:Input
- abstract val moduleProp : MapProperty
-
- @InputDirectory
- val inputDir = variant.map { project.layout.buildDirectory.dir("outputs/apk/${it}") }
-
- @OutputDirectory
- val outputDir = variant.map { project.layout.buildDirectory.dir("magisk/${it}") }
-
- @TaskAction
- fun build() {
- val dir = outputDir.get().get()
- project.mkdir(dir)
- val systemExtDir = dir.dir("system/system_ext")
- val permDir = dir.dir("system/system_ext/etc/permissions")
- val appDir = systemExtDir.dir("priv-app/${appName.get()}")
- val metaInfDir = dir.dir("META-INF/com/google/android")
- project.mkdir(systemExtDir)
- project.mkdir(metaInfDir)
- project.mkdir(appDir)
- project.mkdir(permDir)
- project.copy {
- into(appDir)
- from(inputDir) {
- include("app-${variant.get()}.apk")
- rename("app-${variant.get()}.apk", "${appName.get()}.apk")
- }
- }
- project.copy {
- from(permsFile)
- into(permDir)
- }
- project.copy {
- from(moduleInstaller)
- into(metaInfDir)
- rename(".*", "update-binary")
- }
- dir.file("customize.sh").asFile.writeText(moduleCustomizeScriptText.get())
- dir.file("uninstall.sh").asFile.writeText(moduleUninstallScriptText.get())
- metaInfDir.file("updater-script").asFile.writeText("#MAGISK\n")
- dir.file("module.prop").asFile.writeText(moduleProp.get().map { (k, v) -> "$k=$v" }.joinToString("\n"))
- }
-}
\ No newline at end of file
diff --git a/buildSrc/src/main/kotlin/im/angry/openeuicc/build/Versioning.kt b/buildSrc/src/main/kotlin/im/angry/openeuicc/build/Versioning.kt
index ef07c43..24f0235 100644
--- a/buildSrc/src/main/kotlin/im/angry/openeuicc/build/Versioning.kt
+++ b/buildSrc/src/main/kotlin/im/angry/openeuicc/build/Versioning.kt
@@ -16,7 +16,7 @@ val Project.gitVersionCode: Int
standardOutput = stdout
}
stdout.toString("utf-8").trim('\n').toInt()
- } catch (_: Exception) {
+ } catch (e: Exception) {
0
}
@@ -29,7 +29,7 @@ val Project.gitVersionName: String
standardOutput = stdout
}
stdout.toString("utf-8").trim('\n')
- } catch (_: Exception) {
+ } catch (e: Exception) {
"Unknown"
}
@@ -38,7 +38,7 @@ class MyVersioningPlugin: Plugin {
target.configure {
defaultConfig {
versionCode = target.gitVersionCode
- versionName = target.gitVersionName.removePrefix("unpriv-")
+ versionName = target.gitVersionName
}
applicationVariants.all {
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index a4b76b9..e708b1c 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 9355b41..dab3589 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,7 +1,6 @@
+#Wed Jun 08 13:28:20 EDT 2022
distributionBase=GRADLE_USER_HOME
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
-networkTimeout=10000
-validateDistributionUrl=true
-zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
diff --git a/gradlew b/gradlew
index f5feea6..4f906e0 100755
--- a/gradlew
+++ b/gradlew
@@ -1,7 +1,7 @@
-#!/bin/sh
+#!/usr/bin/env sh
#
-# Copyright © 2015-2021 the original authors.
+# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -15,104 +15,69 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-# SPDX-License-Identifier: Apache-2.0
-#
##############################################################################
-#
-# Gradle start up script for POSIX generated by Gradle.
-#
-# Important for running:
-#
-# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
-# noncompliant, but you have some other compliant shell such as ksh or
-# bash, then to run this script, type that shell name before the whole
-# command line, like:
-#
-# ksh Gradle
-#
-# Busybox and similar reduced shells will NOT work, because this script
-# requires all of these POSIX shell features:
-# * functions;
-# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
-# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
-# * compound commands having a testable exit status, especially «case»;
-# * various built-in commands including «command», «set», and «ulimit».
-#
-# Important for patching:
-#
-# (2) This script targets any POSIX shell, so it avoids extensions provided
-# by Bash, Ksh, etc; in particular arrays are avoided.
-#
-# The "traditional" practice of packing multiple parameters into a
-# space-separated string is a well documented source of bugs and security
-# problems, so this is (mostly) avoided, by progressively accumulating
-# options in "$@", and eventually passing that to Java.
-#
-# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
-# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
-# see the in-line comments for details.
-#
-# There are tweaks for specific operating systems such as AIX, CygWin,
-# Darwin, MinGW, and NonStop.
-#
-# (3) This script is generated from the Groovy template
-# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
-# within the Gradle project.
-#
-# You can find Gradle at https://github.com/gradle/gradle/.
-#
+##
+## Gradle start up script for UN*X
+##
##############################################################################
# Attempt to set APP_HOME
-
# Resolve links: $0 may be a link
-app_path=$0
-
-# Need this for daisy-chained symlinks.
-while
- APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
- [ -h "$app_path" ]
-do
- ls=$( ls -ld "$app_path" )
- link=${ls#*' -> '}
- case $link in #(
- /*) app_path=$link ;; #(
- *) app_path=$APP_HOME$link ;;
- esac
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
-# This is normally unused
-# shellcheck disable=SC2034
-APP_BASE_NAME=${0##*/}
-# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
-APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
-' "$PWD" ) || exit
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD=maximum
+MAX_FD="maximum"
warn () {
echo "$*"
-} >&2
+}
die () {
echo
echo "$*"
echo
exit 1
-} >&2
+}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
-case "$( uname )" in #(
- CYGWIN* ) cygwin=true ;; #(
- Darwin* ) darwin=true ;; #(
- MSYS* | MINGW* ) msys=true ;; #(
- NONSTOP* ) nonstop=true ;;
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@@ -122,9 +87,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
- JAVACMD=$JAVA_HOME/jre/sh/java
+ JAVACMD="$JAVA_HOME/jre/sh/java"
else
- JAVACMD=$JAVA_HOME/bin/java
+ JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -133,120 +98,88 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
- JAVACMD=java
- if ! command -v java >/dev/null 2>&1
- then
- die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
- fi
fi
# Increase the maximum file descriptors if we can.
-if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
- case $MAX_FD in #(
- max*)
- # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
- # shellcheck disable=SC2039,SC3045
- MAX_FD=$( ulimit -H -n ) ||
- warn "Could not query maximum file descriptor limit"
- esac
- case $MAX_FD in #(
- '' | soft) :;; #(
- *)
- # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
- # shellcheck disable=SC2039,SC3045
- ulimit -n "$MAX_FD" ||
- warn "Could not set maximum file descriptor limit to $MAX_FD"
- esac
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
fi
-# Collect all arguments for the java command, stacking in reverse order:
-# * args from the command line
-# * the main class name
-# * -classpath
-# * -D...appname settings
-# * --module-path (only if needed)
-# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
# For Cygwin or MSYS, switch paths to Windows format before running java
-if "$cygwin" || "$msys" ; then
- APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
- CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
- JAVACMD=$( cygpath --unix "$JAVACMD" )
+ JAVACMD=`cygpath --unix "$JAVACMD"`
- # Now convert the arguments - kludge to limit ourselves to /bin/sh
- for arg do
- if
- case $arg in #(
- -*) false ;; # don't mess with options #(
- /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
- [ -e "$t" ] ;; #(
- *) false ;;
- esac
- then
- arg=$( cygpath --path --ignore --mixed "$arg" )
- fi
- # Roll the args list around exactly as many times as the number of
- # args, so each arg winds up back in the position where it started, but
- # possibly modified.
- #
- # NB: a `for` loop captures its iteration list before it begins, so
- # changing the positional parameters here affects neither the number of
- # iterations, nor the values presented in `arg`.
- shift # remove old arg
- set -- "$@" "$arg" # push replacement arg
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
fi
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
-
-# Collect all arguments for the java command:
-# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
-# and any embedded shellness will be escaped.
-# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
-# treated as '${Hostname}' itself on the command line.
-
-set -- \
- "-Dorg.gradle.appname=$APP_BASE_NAME" \
- -classpath "$CLASSPATH" \
- org.gradle.wrapper.GradleWrapperMain \
- "$@"
-
-# Stop when "xargs" is not available.
-if ! command -v xargs >/dev/null 2>&1
-then
- die "xargs is not available"
-fi
-
-# Use "xargs" to parse quoted args.
-#
-# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
-#
-# In Bash we could simply go:
-#
-# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
-# set -- "${ARGS[@]}" "$@"
-#
-# but POSIX shell has neither arrays nor command substitution, so instead we
-# post-process each arg (as a line of input to sed) to backslash-escape any
-# character that might be a shell metacharacter, then use eval to reverse
-# that process (while maintaining the separation between arguments), and wrap
-# the whole thing up as a single "set" statement.
-#
-# This will of course break if any of these variables contains a newline or
-# an unmatched quote.
-#
-
-eval "set -- $(
- printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
- xargs -n1 |
- sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
- tr '\n' ' '
- )" '"$@"'
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
index 9b42019..ac1b06f 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -13,10 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
-@rem SPDX-License-Identifier: Apache-2.0
-@rem
-@if "%DEBUG%"=="" @echo off
+@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -27,8 +25,7 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
-if "%DIRNAME%"=="" set DIRNAME=.
-@rem This is normally unused
+if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@@ -43,13 +40,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
-if %ERRORLEVEL% equ 0 goto execute
+if "%ERRORLEVEL%" == "0" goto execute
-echo. 1>&2
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
-echo. 1>&2
-echo Please set the JAVA_HOME variable in your environment to match the 1>&2
-echo location of your Java installation. 1>&2
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
goto fail
@@ -59,11 +56,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
-echo. 1>&2
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
-echo. 1>&2
-echo Please set the JAVA_HOME variable in your environment to match the 1>&2
-echo location of your Java installation. 1>&2
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
goto fail
@@ -78,15 +75,13 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
-if %ERRORLEVEL% equ 0 goto mainEnd
+if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
-set EXIT_CODE=%ERRORLEVEL%
-if %EXIT_CODE% equ 0 set EXIT_CODE=1
-if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
-exit /b %EXIT_CODE%
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt
index ce09717..3674f4f 100644
--- a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt
+++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt
@@ -11,14 +11,12 @@ import net.typeblog.lpac_jni.LocalProfileInfo
import net.typeblog.lpac_jni.LocalProfileNotification
import net.typeblog.lpac_jni.ProfileDownloadCallback
import net.typeblog.lpac_jni.Version
-import java.util.concurrent.locks.ReentrantLock
-import kotlin.concurrent.withLock
class LocalProfileAssistantImpl(
isdrAid: ByteArray,
rawApduInterface: ApduInterface,
rawHttpInterface: HttpInterface
-) : LocalProfileAssistant {
+): LocalProfileAssistant {
companion object {
private const val TAG = "LocalProfileAssistantImpl"
}
@@ -76,10 +74,6 @@ class LocalProfileAssistantImpl(
}
}
- // Controls concurrency of every single method in this class, since
- // the C-side is explicitly NOT thread-safe
- private val lock = ReentrantLock()
-
private val apduInterface = ApduInterfaceWrapper(rawApduInterface)
private val httpInterface = HttpInterfaceWrapper(rawHttpInterface)
@@ -111,24 +105,23 @@ class LocalProfileAssistantImpl(
}
override val profiles: List
- get() = lock.withLock {
+ @Synchronized
+ get() {
val head = LpacJni.es10cGetProfilesInfo(contextHandle)
var curr = head
val ret = mutableListOf()
while (curr != 0L) {
val state = LocalProfileInfo.State.fromString(LpacJni.profileGetStateString(curr))
val clazz = LocalProfileInfo.Clazz.fromString(LpacJni.profileGetClassString(curr))
- ret.add(
- LocalProfileInfo(
- LpacJni.profileGetIccid(curr),
- state,
- LpacJni.profileGetName(curr),
- LpacJni.profileGetNickname(curr),
- LpacJni.profileGetServiceProvider(curr),
- LpacJni.profileGetIsdpAid(curr),
- clazz
- )
- )
+ ret.add(LocalProfileInfo(
+ LpacJni.profileGetIccid(curr),
+ state,
+ LpacJni.profileGetName(curr),
+ LpacJni.profileGetNickname(curr),
+ LpacJni.profileGetServiceProvider(curr),
+ LpacJni.profileGetIsdpAid(curr),
+ clazz
+ ))
curr = LpacJni.profilesNext(curr)
}
@@ -137,87 +130,79 @@ class LocalProfileAssistantImpl(
}
override val notifications: List
- get() = lock.withLock {
+ @Synchronized
+ get() {
val head = LpacJni.es10bListNotification(contextHandle)
var curr = head
-
- try {
- val ret = mutableListOf()
- while (curr != 0L) {
- ret.add(
- LocalProfileNotification(
- LpacJni.notificationGetSeq(curr),
- LocalProfileNotification.Operation.fromString(
- LpacJni.notificationGetOperationString(
- curr
- )
- ),
- LpacJni.notificationGetAddress(curr),
- LpacJni.notificationGetIccid(curr),
- )
- )
- curr = LpacJni.notificationsNext(curr)
- }
- return ret.sortedBy { it.seqNumber }.reversed()
- } finally {
- LpacJni.notificationsFree(head)
+ val ret = mutableListOf()
+ while (curr != 0L) {
+ ret.add(LocalProfileNotification(
+ LpacJni.notificationGetSeq(curr),
+ LocalProfileNotification.Operation.fromString(LpacJni.notificationGetOperationString(curr)),
+ LpacJni.notificationGetAddress(curr),
+ LpacJni.notificationGetIccid(curr),
+ ))
+ curr = LpacJni.notificationsNext(curr)
}
+ LpacJni.notificationsFree(head)
+ return ret.sortedBy { it.seqNumber }.reversed()
}
override val eID: String
- get() = lock.withLock { LpacJni.es10cGetEid(contextHandle)!! }
+ @Synchronized
+ get() = LpacJni.es10cGetEid(contextHandle)!!
override val euiccInfo2: EuiccInfo2?
- get() = lock.withLock {
+ @Synchronized
+ get() {
val cInfo = LpacJni.es10cexGetEuiccInfo2(contextHandle)
if (cInfo == 0L) return null
- try {
- return EuiccInfo2(
- Version(LpacJni.euiccInfo2GetSGP22Version(cInfo)),
- Version(LpacJni.euiccInfo2GetProfileVersion(cInfo)),
- Version(LpacJni.euiccInfo2GetEuiccFirmwareVersion(cInfo)),
- Version(LpacJni.euiccInfo2GetGlobalPlatformVersion(cInfo)),
- LpacJni.euiccInfo2GetSasAcreditationNumber(cInfo),
- Version(LpacJni.euiccInfo2GetPpVersion(cInfo)),
- LpacJni.euiccInfo2GetFreeNonVolatileMemory(cInfo).toInt(),
- LpacJni.euiccInfo2GetFreeVolatileMemory(cInfo).toInt(),
- buildSet {
- var cursor = LpacJni.euiccInfo2GetEuiccCiPKIdListForSigning(cInfo)
- while (cursor != 0L) {
- add(LpacJni.stringDeref(cursor))
- cursor = LpacJni.stringArrNext(cursor)
- }
- },
- buildSet {
- var cursor = LpacJni.euiccInfo2GetEuiccCiPKIdListForVerification(cInfo)
- while (cursor != 0L) {
- add(LpacJni.stringDeref(cursor))
- cursor = LpacJni.stringArrNext(cursor)
- }
- },
- )
- } finally {
- LpacJni.euiccInfo2Free(cInfo)
- }
+ val ret = EuiccInfo2(
+ Version(LpacJni.euiccInfo2GetSGP22Version(cInfo)),
+ Version(LpacJni.euiccInfo2GetProfileVersion(cInfo)),
+ Version(LpacJni.euiccInfo2GetEuiccFirmwareVersion(cInfo)),
+ Version(LpacJni.euiccInfo2GetGlobalPlatformVersion(cInfo)),
+ LpacJni.euiccInfo2GetSasAcreditationNumber(cInfo),
+ Version(LpacJni.euiccInfo2GetPpVersion(cInfo)),
+ LpacJni.euiccInfo2GetFreeNonVolatileMemory(cInfo).toInt(),
+ LpacJni.euiccInfo2GetFreeVolatileMemory(cInfo).toInt(),
+ buildSet {
+ var cursor = LpacJni.euiccInfo2GetEuiccCiPKIdListForSigning(cInfo)
+ while (cursor != 0L) {
+ add(LpacJni.stringDeref(cursor))
+ cursor = LpacJni.stringArrNext(cursor)
+ }
+ },
+ buildSet {
+ var cursor = LpacJni.euiccInfo2GetEuiccCiPKIdListForVerification(cInfo)
+ while (cursor != 0L) {
+ add(LpacJni.stringDeref(cursor))
+ cursor = LpacJni.stringArrNext(cursor)
+ }
+ },
+ )
+
+ LpacJni.euiccInfo2Free(cInfo)
+
+ return ret
}
- override fun enableProfile(iccid: String, refresh: Boolean): Boolean = lock.withLock {
+ @Synchronized
+ override fun enableProfile(iccid: String, refresh: Boolean): Boolean =
LpacJni.es10cEnableProfile(contextHandle, iccid, refresh) == 0
- }
- override fun disableProfile(iccid: String, refresh: Boolean): Boolean = lock.withLock {
+ @Synchronized
+ override fun disableProfile(iccid: String, refresh: Boolean): Boolean =
LpacJni.es10cDisableProfile(contextHandle, iccid, refresh) == 0
- }
- override fun deleteProfile(iccid: String): Boolean = lock.withLock {
+ @Synchronized
+ override fun deleteProfile(iccid: String): Boolean =
LpacJni.es10cDeleteProfile(contextHandle, iccid) == 0
- }
- override fun downloadProfile(
- smdp: String, matchingId: String?, imei: String?,
- confirmationCode: String?, callback: ProfileDownloadCallback
- ) = lock.withLock {
+ @Synchronized
+ override fun downloadProfile(smdp: String, matchingId: String?, imei: String?,
+ confirmationCode: String?, callback: ProfileDownloadCallback) {
val res = LpacJni.downloadProfile(
contextHandle,
smdp,
@@ -244,17 +229,18 @@ class LocalProfileAssistantImpl(
}
}
- override fun deleteNotification(seqNumber: Long): Boolean = lock.withLock {
+ @Synchronized
+ override fun deleteNotification(seqNumber: Long): Boolean =
LpacJni.es10bDeleteNotification(contextHandle, seqNumber) == 0
- }
- override fun handleNotification(seqNumber: Long): Boolean = lock.withLock {
+ @Synchronized
+ override fun handleNotification(seqNumber: Long): Boolean =
LpacJni.handleNotification(contextHandle, seqNumber).also {
Log.d(TAG, "handleNotification $seqNumber = $it")
} == 0
- }
- override fun setNickname(iccid: String, nickname: String) = lock.withLock {
+ @Synchronized
+ override fun setNickname(iccid: String, nickname: String) {
val encoded = try {
Charsets.UTF_8.encode(nickname).array()
} catch (e: CharacterCodingException) {
@@ -273,12 +259,11 @@ class LocalProfileAssistantImpl(
}
override fun euiccMemoryReset() {
- lock.withLock {
- LpacJni.es10cEuiccMemoryReset(contextHandle)
- }
+ LpacJni.es10cEuiccMemoryReset(contextHandle)
}
- override fun close() = lock.withLock {
+ @Synchronized
+ override fun close() {
if (!finalized) {
LpacJni.euiccFini(contextHandle)
LpacJni.destroyContext(contextHandle)
diff --git a/libs/lpac-jni/src/main/jni/Application.mk b/libs/lpac-jni/src/main/jni/Application.mk
index c1d3766..2112196 100644
--- a/libs/lpac-jni/src/main/jni/Application.mk
+++ b/libs/lpac-jni/src/main/jni/Application.mk
@@ -1,5 +1,4 @@
APP_ABI := all
APP_SHORT_COMMANDS := true
APP_CFLAGS := -Wno-compound-token-split-by-macro
-APP_LDFLAGS := -Wl,--build-id=none -z muldefs
-APP_SUPPORT_FLEXIBLE_PAGE_SIZES := true
+APP_LDFLAGS := -Wl,--build-id=none -z muldefs
\ No newline at end of file
diff --git a/libs/lpac-jni/src/main/jni/lpac-jni.mk b/libs/lpac-jni/src/main/jni/lpac-jni.mk
index dad173c..c0bcee7 100644
--- a/libs/lpac-jni/src/main/jni/lpac-jni.mk
+++ b/libs/lpac-jni/src/main/jni/lpac-jni.mk
@@ -1,5 +1,4 @@
LOCAL_PATH := $(call my-dir)
-LOCAL_LDFLAGS += "-Wl,-z,max-page-size=16384"
# function to find all *.c files under a directory
define all-c-files-under
diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.c b/libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.c
index 007e80d..a61fc96 100644
--- a/libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.c
+++ b/libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.c
@@ -80,7 +80,7 @@ apdu_interface_transmit(struct euicc_ctx *ctx, uint8_t **rx, uint32_t *rx_len, c
LPAC_JNI_EXCEPTION_RETURN;
*rx_len = (*env)->GetArrayLength(env, ret);
*rx = calloc(*rx_len, sizeof(uint8_t));
- (*env)->GetByteArrayRegion(env, ret, 0, *rx_len, (jbyte *) *rx);
+ (*env)->GetByteArrayRegion(env, ret, 0, *rx_len, *rx);
(*env)->DeleteLocalRef(env, txArr);
(*env)->DeleteLocalRef(env, ret);
return 0;
@@ -113,7 +113,7 @@ http_interface_transmit(struct euicc_ctx *ctx, const char *url, uint32_t *rcode,
jbyteArray rxArr = (jbyteArray) (*env)->GetObjectField(env, ret, field_resp_data);
*rx_len = (*env)->GetArrayLength(env, rxArr);
*rx = calloc(*rx_len, sizeof(uint8_t));
- (*env)->GetByteArrayRegion(env, rxArr, 0, *rx_len, (jbyte *) *rx);
+ (*env)->GetByteArrayRegion(env, rxArr, 0, *rx_len, *rx);
(*env)->DeleteLocalRef(env, txArr);
(*env)->DeleteLocalRef(env, rxArr);
(*env)->DeleteLocalRef(env, headersArr);