Compare commits

...
Sign in to create a new pull request.

19 commits

Author SHA1 Message Date
c74c1f309c docs: enhance README formatting and clarity (#235)
Reviewed-on: PeterCxy/OpenEUICC#235
Co-authored-by: septs <github@septs.pw>
Co-committed-by: septs <github@septs.pw>
2025-10-02 14:25:45 +02:00
b245a9c893 fix magisk module bug (#234)
部分设备magisk模块安装出现失败情况,对此进行修复
如图为在magisk27.1中无法安装

Reviewed-on: PeterCxy/OpenEUICC#234
Co-authored-by: MasterOfStar <hzh4363703@live.com>
Co-committed-by: MasterOfStar <hzh4363703@live.com>
2025-10-02 14:24:51 +02:00
351567a972 chore: improve variant compare (#230)
Reviewed-on: PeterCxy/OpenEUICC#230
Co-authored-by: septs <github@septs.pw>
Co-committed-by: septs <github@septs.pw>
2025-09-22 00:50:30 +02:00
0096b9005f chore: update ES10x MSS preference labels for clarity (#231)
Reviewed-on: PeterCxy/OpenEUICC#231
Co-authored-by: septs <github@septs.pw>
Co-committed-by: septs <github@septs.pw>
2025-09-22 00:50:16 +02:00
525212c1b8 Update Japanese Language (#229)
日本語を更新しといたよ。
間違ったところも修正済み。

Reviewed-on: PeterCxy/OpenEUICC#229
Co-authored-by: reindex <reindex@noreply.gitea.angry.im>
Co-committed-by: reindex <reindex@noreply.gitea.angry.im>
2025-09-22 00:49:57 +02:00
bf30f1478c Update README.md 2025-09-14 14:48:32 -04:00
2cda633fd0 lpac-jni: Convert all @Synchronized usage to a ReentrantLock 2025-09-08 21:58:53 -04:00
7db1c1ea9d lpac-jni: Add missing synchronized annotation 2025-09-08 21:48:41 -04:00
d46461bd2d lpac-jni: Prevent potential C-side memory leak 2025-09-08 21:47:13 -04:00
472f9d05ac chore: simplify progress item initialization and error message handling (#226)
Reviewed-on: PeterCxy/OpenEUICC#226
Co-authored-by: septs <github@septs.pw>
Co-committed-by: septs <github@septs.pw>
2025-09-08 15:12:26 +02:00
0c98121c2a chore: ignore new file in dot-idea directory (#227)
![CleanShot 2025-09-08 at 15.51.35@2x](/attachments/8133796b-eebf-4162-b6c4-f66d9217b48d)

Reviewed-on: PeterCxy/OpenEUICC#227
Co-authored-by: septs <github@septs.pw>
Co-committed-by: septs <github@septs.pw>
2025-09-08 15:11:40 +02:00
acfeda8dc9 i18n: Translate new strings added recently 2025-09-07 17:45:18 -04:00
7bae82daf9 chore: add nvram hint (#223)
Reviewed-on: PeterCxy/OpenEUICC#223
Co-authored-by: septs <github@septs.pw>
Co-committed-by: septs <github@septs.pw>
2025-09-07 21:28:05 +02:00
cce247e747 feat: Display simplified error messages when profile downloading fails
i18n pending

Co-Authored-By: septs <github@septs.pw>
2025-09-07 14:56:49 -04:00
915a05634b Add updateJson to Magisk module props 2025-08-17 09:42:09 -04:00
9e40232ed0 feat: es10x mss as preference (#213)
Reviewed-on: PeterCxy/OpenEUICC#213
Co-authored-by: septs <github@septs.pw>
Co-committed-by: septs <github@septs.pw>
2025-08-17 02:45:48 +02:00
27b7e50b97 refactor: simplify developer options click handling and toast messages (#221)
Reviewed-on: PeterCxy/OpenEUICC#221
Co-authored-by: septs <github@septs.pw>
Co-committed-by: septs <github@septs.pw>
2025-08-17 02:37:16 +02:00
7e7f5c2b05 feat: Magisk module builds on CI
Much of this is taken from AndroPlus's Magisk module repo, thanks!

There's still no release builds for privileged OpenEUICC and the Magisk
module; this is still intentional (for now). However, it is possible to
produce a release Magisk zip locally via the
`:app:assembleReleaseMagiskModule` task.
2025-08-15 21:06:23 -04:00
4d8b8e8fb5 fix: update .gitignore to ignore all deployment target files (#222)
Reviewed-on: PeterCxy/OpenEUICC#222
Co-authored-by: septs <github@septs.pw>
Co-committed-by: septs <github@septs.pw>
2025-08-15 00:18:41 +02:00
26 changed files with 820 additions and 305 deletions

View file

@ -33,14 +33,23 @@ jobs:
uses: https://gitea.angry.im/actions/setup-android@v3
- name: Build Debug APKs
run: ./gradlew --no-daemon assembleDebug
run: ./gradlew --no-daemon assembleDebug :app:assembleDebugMagiskModuleDir
- name: Copy Artifacts
run: find . -name 'app*-debug.apk' -exec cp {} . \;
run: |
find . -name 'app*-debug.apk' -exec cp {} . \;
cp -r app/build/magisk/debug ./magisk-debug
- name: Upload Artifacts
- name: Upload APK 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

21
.idea/.gitignore generated vendored
View file

@ -1,14 +1,7 @@
/shelf
/caches
/libraries
/assetWizardSettings.xml
/deploymentTargetDropDown.xml
/gradle.xml
/misc.xml
/modules.xml
/navEditor.xml
/runConfigurations.xml
/workspace.xml
/AndroidProjectSystem.xml
**/*.iml
*
!/codeStyles/Project.xml
!/codeStyles/codeStyleConfig.xml
!/vcs.xml
!/kotlinc.xml
!/compiler.xml
!/migrations.xml

View file

@ -1,37 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetSelector">
<selectionStates>
<SelectionState runConfigName="app-unpriv">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
<SelectionState runConfigName="app-unpriv.androidTest">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
<SelectionState runConfigName="app-unpriv.main">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
<SelectionState runConfigName="app-unpriv.unitTest">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
<SelectionState runConfigName="app.unitTest">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
<SelectionState runConfigName="app.androidTest">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
<SelectionState runConfigName="app.main">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
<SelectionState runConfigName="workspace.OpenEUICC.app-unpriv">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
<SelectionState runConfigName="workspace.OpenEUICC.app">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
</selectionStates>
</component>
</project>

View file

@ -4,27 +4,44 @@ A fully free and open-source Local Profile Assistant implementation for Android
There are two variants of this project, OpenEUICC and EasyEUICC:
| | OpenEUICC | EasyEUICC |
|:------------------------------|:-----------------------------------------------:|:-----------------:|
| Privileged | Must be installed as system app | No |
| Internal eSIM | Supported | Unsupported |
| External (Removable) eSIM | Supported | Supported |
| USB Readers | Supported | Supported |
| Requires allowlisting by eSIM | No | Yes -- except USB |
| System Integration | Partial (carrier partner API unimplemented yet) | No |
| | 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 |
[^1]: Also known as "Removable eSIM"
[^2]: Carrier Partner API unimplemented yet
Some side notes:
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](https://en.wikipedia.org/wiki/CCID_(protocol)) are supported.
3. Prebuilt release-mode EasyEUICC apks can be downloaded [here](https://gitea.angry.im/PeterCxy/OpenEUICC/releases). For OpenEUICC, no official release is currently provided and only debug mode APKs can be found in the CI page.
4. For removable eSIM chip vendors: to have your chip supported by official builds of EasyEUICC when inserted, include the ARA-M hash `2A2FA878BC7C3354C2CF82935A5945A3EDAE4AFA`.
__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.
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`.
__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.__
[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"
Building (Gradle)
===
**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)
Make sure you have all submodules cloned and updated by running
@ -57,8 +74,7 @@ For EasyEUICC:
./gradlew :app-unpriv:assembleRelease
```
Building (AOSP)
===
# Building (AOSP)
There are two ways to include OpenEUICC in your AOSP-based system image:
@ -68,25 +84,22 @@ 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.
FAQs
===
[`privapp_whitelist_im.angry.openeuicc.xml`]: privapp_whitelist_im.angry.openeuicc.xml "OpenEUICC Privapp Whitelist"
- 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.
# FAQs
- 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: 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: 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/`:
@ -126,4 +139,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.

View file

@ -21,7 +21,7 @@ open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccCha
override suspend fun tryOpenEuiccChannel(
port: UiccPortInfoCompat,
isdrAid: ByteArray
): EuiccChannel? {
): EuiccChannel? = try {
if (port.portIndex != 0) {
Log.w(
DefaultEuiccChannelManager.TAG,
@ -35,58 +35,52 @@ open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccCha
DefaultEuiccChannelManager.TAG,
"Trying OMAPI for physical slot ${port.card.physicalSlotIndex}"
)
try {
return EuiccChannelImpl(
context.getString(R.string.channel_type_omapi),
EuiccChannelImpl(
context.getString(R.string.channel_type_omapi),
port,
intrinsicChannelName = null,
OmapiApduInterface(
seService!!,
port,
intrinsicChannelName = null,
OmapiApduInterface(
seService!!,
port,
context.preferenceRepository.verboseLoggingFlow
),
isdrAid,
context.preferenceRepository.verboseLoggingFlow,
context.preferenceRepository.ignoreTLSCertificateFlow,
).also {
Log.i(DefaultEuiccChannelManager.TAG, "Is OMAPI channel, setting MSS to 60")
it.lpa.setEs10xMss(60)
}
} catch (_: IllegalArgumentException) {
// Failed
Log.w(
DefaultEuiccChannelManager.TAG,
"OMAPI APDU interface unavailable for physical slot ${port.card.physicalSlotIndex} with ISD-R AID: ${isdrAid.encodeHex()}."
)
}
return null
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
}
override fun tryOpenUsbEuiccChannel(
ccidCtx: UsbCcidContext,
isdrAid: ByteArray
): EuiccChannel? {
try {
return EuiccChannelImpl(
context.getString(R.string.channel_type_usb),
FakeUiccPortInfoCompat(FakeUiccCardInfoCompat(EuiccChannelManager.USB_CHANNEL_ID)),
intrinsicChannelName = ccidCtx.productName,
UsbApduInterface(
ccidCtx
),
isdrAid,
context.preferenceRepository.verboseLoggingFlow,
context.preferenceRepository.ignoreTLSCertificateFlow,
)
} catch (_: IllegalArgumentException) {
// Failed
Log.w(
DefaultEuiccChannelManager.TAG,
"USB APDU interface unavailable for ISD-R AID: ${isdrAid.encodeHex()}."
)
}
return null
): EuiccChannel? = try {
EuiccChannelImpl(
context.getString(R.string.channel_type_usb),
FakeUiccPortInfoCompat(FakeUiccCardInfoCompat(EuiccChannelManager.USB_CHANNEL_ID)),
intrinsicChannelName = ccidCtx.productName,
UsbApduInterface(
ccidCtx
),
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() {

View file

@ -1,8 +1,9 @@
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
@ -15,7 +16,8 @@ class EuiccChannelImpl(
override val apduInterface: ApduInterface,
override val isdrAid: ByteArray,
verboseLoggingFlow: Flow<Boolean>,
ignoreTLSCertificateFlow: Flow<Boolean>
ignoreTLSCertificateFlow: Flow<Boolean>,
es10xMssFlow: Flow<Int>,
) : EuiccChannel {
override val slotId = port.card.physicalSlotIndex
override val logicalSlotId = port.logicalSlotIndex
@ -25,8 +27,10 @@ class EuiccChannelImpl(
LocalProfileAssistantImpl(
isdrAid,
apduInterface,
HttpInterfaceImpl(verboseLoggingFlow, ignoreTLSCertificateFlow)
)
HttpInterfaceImpl(verboseLoggingFlow, ignoreTLSCertificateFlow),
).also {
it.setEs10xMss(runBlocking { es10xMssFlow.first().toByte() })
}
override val atr: ByteArray?
get() = (apduInterface as? ApduInterfaceAtrProvider)?.atr

View file

@ -123,7 +123,13 @@ class EuiccInfoActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
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())) }
add(Item(R.string.euicc_info_free_nvram, info.freeNvram.let(::formatFreeSpace)))
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?.euiccCiPKIdListForSigning.orEmpty().let { signers ->
// SGP.28 v1.0, eSIM CI Registration Criteria (Page 5 of 9, 2019-10-24)

View file

@ -8,6 +8,7 @@ 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
@ -16,7 +17,6 @@ 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.isVisible = it }
.onEach(developerPref::setVisible)
.collect()
}
@ -84,6 +84,9 @@ open class SettingsFragment: PreferenceFragmentCompat() {
requirePreference<CheckBoxPreference>("pref_developer_euicc_memory_reset")
.bindBooleanFlow(preferenceRepository.euiccMemoryResetFlow)
requirePreference<ListPreference>("pref_developer_es10x_mss")
.bindIntFlow(preferenceRepository.es10xMssFlow, 63)
requirePreference<Preference>("pref_developer_isdr_aid_list").apply {
intent = Intent(requireContext(), IsdrAidListActivity::class.java)
}
@ -100,51 +103,53 @@ open class SettingsFragment: PreferenceFragmentCompat() {
@Suppress("UNUSED_PARAMETER")
private fun onAppVersionClicked(pref: Preference): Boolean {
if (developerPref.isVisible) return false
val now = System.currentTimeMillis()
if (now - lastClickTimestamp >= 1000) {
numClicks = 1
} else {
numClicks++
}
numClicks = if (now - lastClickTimestamp >= 1000) 1 else numClicks + 1
lastClickTimestamp = now
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()
lifecycleScope.launch {
preferenceRepository.developerOptionsEnabledFlow.updatePreference(numClicks >= 7)
}
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<Boolean>) {
lifecycleScope.launch {
flow.collect { isChecked = it }
flow.collect(::setChecked)
}
setOnPreferenceChangeListener { _, newValue ->
runBlocking {
lifecycleScope.launch {
flow.updatePreference(newValue as Boolean)
}
true
}
}
private fun ListPreference.bindIntFlow(flow: PreferenceFlowWrapper<Int>, 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<PreferenceCategory>(overlayKey)
val targetCat = requirePreference<PreferenceCategory>(targetKey)

View file

@ -7,6 +7,7 @@ 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
@ -42,19 +43,17 @@ class DownloadWizardProgressFragment : DownloadWizardActivity.DownloadWizardStep
}
private data class ProgressItem(
val titleRes: Int,
var state: ProgressState
@StringRes val titleRes: Int,
var state: ProgressState = ProgressState.NotStarted,
var errorMessage: SimplifiedErrorMessages? = null,
)
private val progressItems = arrayOf(
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)
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)
)
private val adapter = ProgressItemAdapter()
@ -122,8 +121,13 @@ 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) {
progressItem.state =
if (state.downloadError == null) ProgressState.Done else ProgressState.Error
if (state.downloadError == null) {
progressItem.state = ProgressState.Done
} else {
progressItem.state = ProgressState.Error
progressItem.errorMessage =
SimplifiedErrorMessages.fromDownloadError(state.downloadError!!)
}
}
adapter.notifyItemChanged(index)
@ -133,9 +137,8 @@ class DownloadWizardProgressFragment : DownloadWizardActivity.DownloadWizardStep
refreshButtons()
}
is EuiccChannelManagerService.ForegroundTaskState.InProgress -> {
is EuiccChannelManagerService.ForegroundTaskState.InProgress ->
updateProgress(it.progress)
}
else -> {}
}
@ -197,9 +200,15 @@ class DownloadWizardProgressFragment : DownloadWizardActivity.DownloadWizardStep
private val progressBar =
root.requireViewById<ProgressBar>(R.id.download_progress_icon_progress)
private val icon = root.requireViewById<ImageView>(R.id.download_progress_icon)
private val errorTitle =
root.requireViewById<TextView>(R.id.download_progress_item_error_title)
private val errorSuggestion =
root.requireViewById<TextView>(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 -> {
@ -222,6 +231,15 @@ 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)
}
}
}
}

View file

@ -0,0 +1,154 @@
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
}
}
}

View file

@ -5,6 +5,7 @@ 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
@ -38,6 +39,7 @@ internal object PreferenceKeys {
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"
@ -89,6 +91,7 @@ open class PreferenceRepository(private val context: Context) {
PreferenceConstants.DEFAULT_AID_LIST,
{ Base64.getEncoder().encodeToString(it.encodeToByteArray()) },
{ Base64.getDecoder().decode(it).decodeToString() })
val es10xMssFlow = bindFlow(PreferenceKeys.ES10X_MSS, 63)
protected fun <T> bindFlow(
key: Preferences.Key<T>,

View file

@ -1,30 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto">
android:layout_height="wrap_content">
<TextView
android:id="@+id/download_progress_item_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:layout_marginHorizontal="20dp"
android:textSize="14sp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
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="@id/download_progress_icon_container"
app:layout_constraintEnd_toStartOf="@id/download_progress_icon_container"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/download_progress_icon_container"
app:layout_constraintVertical_bias="0.5" />
<FrameLayout
android:id="@+id/download_progress_icon_container"
android:layout_margin="20dp"
android:layout_width="30dp"
android:layout_height="30dp"
app:layout_constraintTop_toTopOf="parent"
android:layout_margin="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent">
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0">
<ProgressBar
android:id="@+id/download_progress_icon_progress"
@ -42,4 +44,38 @@
</FrameLayout>
<TextView
android:id="@+id/download_progress_item_error_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:layout_marginBottom="10dp"
android:textColor="?attr/colorError"
android:textSize="12sp"
android:visibility="gone"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toTopOf="@id/download_progress_item_error_suggestion"
app:layout_constraintEnd_toStartOf="@id/download_progress_icon_container"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/download_progress_item_title" />
<TextView
android:id="@+id/download_progress_item_error_suggestion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:textColor="?attr/colorError"
android:textSize="12sp"
android:visibility="gone"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/download_progress_icon_container"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/download_progress_item_title" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -2,13 +2,13 @@
<resources>
<string name="no_euicc">このアプリでアクセスできるリムーバブル eUICC カードがデバイス上で検出されていません。互換性のあるカード挿入または USB リーダーを接続してください。</string>
<string name="no_profile">この eSIM にはプロファイルがありません。</string>
<string name="euicc_info_unknown">不明</string>
<string name="euicc_info_unavailable">情報がありません</string>
<string name="notification_help">ヘルプ</string>
<string name="profile_reload_slots">スロットを再読み込み</string>
<string name="profile_no_enabled_profile">不明</string>
<string name="channel_name_format">論理スロット %d</string>
<string name="profile_state_enabled">有効済み</string>
<string name="profile_state_disabled">無効済み</string>
<!-- Profile -->
<string name="profile_state_enabled">有効化済み</string>
<string name="profile_state_disabled">無効化済み</string>
<string name="profile_provider">プロバイダー:</string>
<string name="profile_class">クラス:</string>
<string name="profile_class_testing">テスト中</string>
@ -22,6 +22,8 @@
<string name="profile_switch_did_not_refresh">操作は成功しましたが、デバイスのモデムが更新を拒否しました。新しいプロファイルを使用するには機内モードに切り替えるか、再起動する必要があります。</string>
<string name="toast_profile_enable_failed">新しい eSIM プロファイルに切り替えることができません。</string>
<string name="toast_profile_delete_confirm_text_mismatched">確認文字列が一致しません</string>
<string name="toast_euicc_memory_reset_confirm_text_mismatched">確認文字列が一致しません</string>
<string name="toast_euicc_memory_reset_finitshed">このチップは消去されました</string>
<string name="toast_iccid_copied">ICCID をクリップボードにコピーしました</string>
<string name="toast_sn_copied">シリアル番号をクリップボードにコピーしました</string>
<string name="toast_eid_copied">EID をクリップボードにコピーしました</string>
@ -38,14 +40,16 @@
<string name="task_profile_delete_failure">eSIM プロファイルの削除に失敗しました</string>
<string name="task_profile_switch">eSIM プロファイルを切り替え中</string>
<string name="task_profile_switch_failure">eSIM プロファイルの切り替えに失敗しました</string>
<string name="task_euicc_memory_reset">eSIM チップを消去中</string>
<string name="task_euicc_memory_reset_failure">eSIM チップの消去に失敗しました</string>
<string name="profile_download">新しい eSIM</string>
<string name="profile_download_server">サーバー (RSP / SM-DP+)</string>
<string name="profile_download_code">アクティベーションコード</string>
<string name="profile_download_confirmation_code">確認コード (オプション)</string>
<string name="profile_download_confirmation_code_required">確認コード (必須)</string>
<string name="profile_download_imei">IMEI (オプション)</string>
<string name="profile_download_low_nvram_title">残り容量が少ない</string>
<string name="profile_download_low_nvram_message">残り容量が少ないため、ダウンロードに失敗する可能性があります。</string>
<string name="profile_download_low_nvram_title">残りの容量が少量です</string>
<string name="profile_download_low_nvram_message">残り容量が少ないため、このプロファイルのダウンロードに失敗する可能性があります。</string>
<string name="profile_download_no_lpa_string">クリップボードに LPA コードがありません</string>
<string name="profile_download_incorrect_lpa_string">解析できません</string>
<string name="profile_download_incorrect_lpa_string_message">QR コードまたはクリップボードの内容を LPA コードとして解析できませんでした。</string>
@ -83,6 +87,27 @@
<string name="download_wizard_diagnostics_last_apdu_exception">最終の APDU 例外:</string>
<string name="download_wizard_diagnostics_save">保存</string>
<string name="download_wizard_diagnostics_file_template">「%s」での診断</string>
<string name="download_wizard_error_iccid_already">この eSIM プロファイルはすでに eSIM チップに存在します。</string>
<string name="download_wizard_error_insufficient_memory">eSIM チップにはプロファイルをダウンロードするのに必要なメモリが残っていません。</string>
<string name="download_wizard_error_unsupported_profile">この eSIM プロファイルは、eSIM チップではサポートされていません。</string>
<string name="download_wizard_error_card_internal_error">eSIM チップでエラーが発生しました。</string>
<string name="download_wizard_error_eid_not_supported">使用しているデバイスまたは eSIM チップの EID は、通信事業者によってサポートされていません。</string>
<string name="download_wizard_error_eid_mismatch">この eSIM プロファイルは、別のデバイスにダウンロードされています。</string>
<string name="download_wizard_error_profile_unreleased">この eSIM プロファイルは取り消されました。</string>
<string name="download_wizard_error_matching_id_refused">アクティベーションコードが無効です。</string>
<string name="download_wizard_error_profile_retries_exceeded">eSIM プロファイルのダウンロード試行回数の上限を超えました。</string>
<string name="download_wizard_error_confirmation_code_missing">このプロファイルをダウンロードするには確認コードが必要です。</string>
<string name="download_wizard_error_confirmation_code_refused">入力した確認コードは無効です。</string>
<string name="download_wizard_error_profile_expired">この eSIM プロファイルは有効期限が切れています。</string>
<string name="download_wizard_error_confirmation_code_retries_exceeded">確認コードのダウンロード試行回数の上限を超えました。</string>
<string name="download_wizard_error_unknown_hostname">不明な SM-DP+ アドレス</string>
<string name="download_wizard_error_network_unreachable">ネットワークにアクセスできません</string>
<string name="download_wizard_error_tls_certificate">TLS 証明書エラー、この eSIM プロファイルはサポートされていません</string>
<string name="download_wizard_error_suggest_profile_installed">ダウンロード済みの eSIM プロファイルを再インストールしようとしています</string>
<string name="download_wizard_error_suggest_insufficient_memory">未使用の eSIM プロファイルをいくつか削除して、再度お試しください</string>
<string name="download_wizard_error_suggest_contact_carrier">サポートについては、通信事業者にお問い合わせください。</string>
<string name="download_wizard_error_suggest_contact_reissue">この eSIM プロファイルを再発行するには、通信事業者にお問い合わせください。</string>
<string name="download_wizard_error_suggest_network_unreachable">別のネットワークに接続後 (例: Wi-Fi とデータを切り替え) を行った後に再度お試しください。</string>
<string name="logs_saved_message">ログは共有したパスに保存されました。別のアプリで共有しますか?</string>
<string name="profile_rename_new_name">新しいニックネーム</string>
<string name="profile_rename_encoding_error">ニックネームを UTF-8 にエンコードできませんでした</string>
@ -96,8 +121,8 @@
<string name="profile_notifications_help">eSIM プロファイルはダウンロードや削除、有効化や無効化されたときに通信事業者に通知を送信できます。送信されるこれらの通知のキューはここにリストされます。\n\n設定では、各タイプの通知を自動的に送信するかどうかを指定できます。通知が送信された場合でもキューのスペースが不足していない限り、記録から自動的に削除されることはありません。\n\nここでは保留中の各通知を手動で送信または削除できます。</string>
<string name="profile_notification_operation_download">ダウンロードしました</string>
<string name="profile_notification_operation_delete">削除しました</string>
<string name="profile_notification_operation_enable">有効化しました</string>
<string name="profile_notification_operation_disable">無効化しました</string>
<string name="profile_notification_operation_enable">有効化済み</string>
<string name="profile_notification_operation_disable">無効化済み</string>
<string name="profile_notification_process">処理</string>
<string name="profile_notification_delete">削除</string>
<string name="euicc_info">eUICC 情報</string>
@ -114,17 +139,29 @@
<string name="euicc_info_sas_accreditation_number">SAS 認定番号</string>
<string name="euicc_info_pp_version">保護されたプロファイルのバージョン</string>
<string name="euicc_info_free_nvram">NVRAM の空き容量 (eSIM プロファイルストレージ)</string>
<string name="euicc_info_free_nvram_hint">(参照用)</string>
<string name="euicc_info_ci_type">証明書発行者 (CI)</string>
<string name="euicc_info_ci_gsma_live">GSMA ライブ CI</string>
<string name="euicc_info_ci_gsma_test">GSMA テスト CI</string>
<string name="euicc_info_ci_unknown">不明な eSIM CI</string>
<string name="euicc_memory_reset">eUICC を消去</string>
<string name="euicc_memory_reset_title">eUICC を消去</string>
<string name="euicc_memory_reset_message">このチップ上のすべてのプロファイルを削除することを確認してください。この操作は元に戻せないことを理解してください。\n\nEID: %1$s\n\n%2$s</string>
<string name="euicc_memory_reset_hint_text">確認のために「%s」を入力してください</string>
<string name="euicc_memory_reset_confirm_text">EID が %s で終わるチップを消去することを確認し、この操作は元に戻せないことを理解してください</string>
<string name="euicc_memory_reset_invoke_button">消去</string>
<!-- eUICC Info -->
<string name="euicc_info_yes">はい</string>
<string name="euicc_info_no">いいえ</string>
<string name="euicc_info_unknown">不明</string>
<string name="euicc_info_unavailable">情報がありません</string>
<string name="logs_save">保存</string>
<string name="logs_filename_template">%s のログ</string>
<string name="developer_options_steps">開発者になるまであと %d ステップです。</string>
<string name="developer_options_enabled">あなたは開発者になりました!</string>
<string name="isdr_aid_list_saved">カスタム ISD-R AID リストが保存されました</string>
<string name="isdr_aid_list">ISD-R AID リスト</string>
<string name="isdr_aid_list_saved">カスタム ISD-R AID リストを保存しました。</string>
<string name="isdr_aid_list_restore_defaults">リセット</string>
<string name="pref_settings">設定</string>
<string name="pref_notifications">通知</string>
<string name="pref_notifications_desc">eSIM のプロファイル操作により、通信事業者に通知が送信されます。必要に応じてこの動作を微調整できます。</string>
@ -136,7 +173,7 @@
<string name="pref_notifications_switch_desc">プロファイルを<i>切り替え中</i>の通知を送信します\nこのタイプの通知は信頼できないことに注意してください。</string>
<string name="pref_advanced">高度な設定</string>
<string name="pref_advanced_disable_safeguard_removable_esim">プロファイルの無効化と削除を許可</string>
<string name="pref_advanced_disable_safeguard_removable_esim_desc">デフォルトでは、このアプリでデバイスに挿入された取り外し可能な eSIM の有効なプロファイルを無効化することを防いでいます。なぜなのかというと<i>時々</i>アクセスができなくなるからです。\nこのチェックボックスを ON にすることで、この保護機能を<i>解除</i>します。</string>
<string name="pref_advanced_disable_safeguard_removable_esim_desc">デフォルトでは、このアプリでデバイスに挿入されたリムーバブル eSIM の有効なプロファイルを無効化することを防いでいます。なぜなのかというと<i>時々</i>アクセスができなくなるからです。\nこのチェックボックスを ON にすることで、この保護機能を<i>解除</i>します。</string>
<string name="pref_advanced_verbose_logging">詳細ログ</string>
<string name="pref_advanced_verbose_logging_desc">詳細ログを有効化します。これには個人的な情報が含まれている可能性があります。この機能を ON にした後は、信頼できるユーザーとのみログを共有してください。</string>
<string name="pref_advanced_language">言語</string>
@ -144,29 +181,22 @@
<string name="pref_advanced_logs">ログ</string>
<string name="pref_advanced_logs_desc">アプリの最新デバッグログを表示します</string>
<string name="pref_developer">開発者オプション</string>
<string name="pref_developer_refresh_after_switch_desc">プロファイルを切り替えた後にモデムに更新コマンドを送信するかどうか。クラッシュが発生する場合は、これを無効にしてみてください。</string>
<string name="pref_developer_refresh_after_switch">モデムに更新コマンドを送信</string>
<string name="pref_developer_refresh_after_switch_desc">プロファイルを切り替えた後にモデムに更新コマンドを送信するかどうかを設定します。クラッシュが発生する場合は、この機能を無効化してください。</string>
<string name="pref_developer_unfiltered_profile_list">フィルタリングされていないプロファイル一覧を表示</string>
<string name="pref_developer_unfiltered_profile_list_desc">非運用のプロファイルも含めます</string>
<string name="pref_developer_ignore_tls_certificate">SM-DP+ TLS 証明書を無視する</string>
<string name="pref_developer_ignore_tls_certificate_desc">RSP サーバーで使用される TLS 証明書を受け入れます</string>
<string name="pref_developer_isdr_aid_list_desc">一部のブランドの取り外し可能な eUICC では、独自の非標準 ISD-R AID が使用されている場合があり、サードパーティ アプリからアクセスできなくなります。アプリはこのリストに追加された非標準の AID の使用を試みる可能性がありますが、動作することは保証されません。</string>
<string name="pref_developer_euicc_memory_reset">eUICC の消去を許可</string>
<string name="pref_developer_euicc_memory_reset_desc">これは危険な操作であり、デフォルトでは非表示になっています。代わりとしてすべてのプロファイルを手動で削除することもできます。</string>
<string name="pref_developer_es10x_mss_desc">グローバル ES10x MSS</string>
<string-array name="pref_developer_es10x_entry_keys">
<item>高速</item>
<item>互換モード</item>
</string-array>
<string name="pref_developer_isdr_aid_list">ISD-R AID リストをカスタマイズ</string>
<string name="pref_developer_isdr_aid_list_desc">一部ブランドのリムーバブル eUICC は独自の非標準な ISD-R AID を使用しているため、サードパーティー製アプリからアクセスできない場合があります。このリストに追加された非標準な AID の使用を試みますが、動作の保証はできません。</string>
<string name="pref_info">情報</string>
<string name="pref_info_app_version">アプリバージョン</string>
<string name="pref_info_source_code">ソースコード</string>
<string name="toast_euicc_memory_reset_confirm_text_mismatched">確認文字列が一致しません</string>
<string name="toast_euicc_memory_reset_finitshed">このチップは消去されました</string>
<string name="task_euicc_memory_reset">eSIM チップを消去しています</string>
<string name="task_euicc_memory_reset_failure">eSIM チップの消去は失敗しました</string>
<string name="euicc_memory_reset">eSIM を消去する</string>
<string name="euicc_memory_reset_title">eSIM を消去する</string>
<string name="euicc_memory_reset_message">このチップ内のすべてのプロファイルを削除することをご確認してください。この操作は元に戻せないことをご理解してください。\n\nEID: %1$s\n\n%2$s</string>
<string name="euicc_memory_reset_hint_text">確認のため、ここに「%s」を入力してください</string>
<string name="euicc_memory_reset_confirm_text">EID が %s で終わるチップを消去することに同意します。これは元に戻せないことを理解しています。</string>
<string name="euicc_memory_reset_invoke_button">消去する</string>
<string name="pref_developer_euicc_memory_reset">eUICC の消去を可能にする</string>
<string name="pref_developer_euicc_memory_reset_desc">この操作は、デフォルトでは非表示になっている危険な操作です。代わりに、すべての構成ファイルを手動で削除することもできます。</string>
<string name="pref_developer_refresh_after_switch">モデムに更新コマンドを送信</string>
<string name="pref_developer_isdr_aid_list">ISD-R AID リストのカスタマイズ</string>
<string name="isdr_aid_list_restore_defaults">リセット</string>
<string name="isdr_aid_list">ISD-R AID リスト</string>
</resources>

View file

@ -5,6 +5,7 @@
<string name="euicc_info_unknown">未知</string>
<string name="notification_help">帮助</string>
<string name="profile_reload_slots">重新加载卡槽</string>
<string name="profile_no_enabled_profile">未知</string>
<string name="channel_name_format">逻辑卡槽 %d</string>
<string name="profile_state_enabled">已启用</string>
<string name="profile_state_disabled">已禁用</string>
@ -46,6 +47,7 @@
<string name="profile_download_imei">IMEI (可选)</string>
<string name="profile_download_low_nvram_title">剩余空间不足</string>
<string name="profile_download_low_nvram_message">当前芯片的剩余空间不足,可能导致配置下载失败。\n是否继续下载</string>
<string name="download_wizard_error_suggest_network_unreachable">请连接到其他网络(例如在 Wi-Fi 和数据之间切换)后重试。</string>
<string name="logs_saved_message">日志已保存到指定路径。需要通过其他 App 分享吗?</string>
<string name="profile_rename_new_name">新昵称</string>
<string name="profile_rename_encoding_error">无法将昵称编码为 UTF-8</string>
@ -83,6 +85,11 @@
<string name="pref_advanced_logs">日志</string>
<string name="pref_advanced_logs_desc">查看应用程序的最新调试日志</string>
<string name="pref_developer_isdr_aid_list_desc">某些品牌的可移除 eUICC 可能会使用自己的非标准 ISD-R AID导致第三方应用无法访问。此 App 可以尝试使用此列表中添加的非标准 AID但不能保证它们一定有效。</string>
<string name="pref_developer_es10x_mss_desc">全局 ES10x MSS</string>
<string-array name="pref_developer_es10x_entry_keys">
<item>最佳效率</item>
<item>最佳兼容性</item>
</string-array>
<string name="pref_info">信息</string>
<string name="pref_info_app_version">App 版本</string>
<string name="pref_info_source_code">源码</string>
@ -136,6 +143,7 @@
<string name="euicc_info_sas_accreditation_number">SAS 认证号码</string>
<string name="euicc_info_pp_version">Protected Profile 版本</string>
<string name="euicc_info_free_nvram">NVRAM 剩余空间 (eSIM 存储容量)</string>
<string name="euicc_info_free_nvram_hint">(仅供参考)</string>
<string name="euicc_info_ci_type">证书签发者 (CI)</string>
<string name="euicc_info_ci_gsma_live">GSMA 生产环境 CI</string>
<string name="euicc_info_ci_gsma_test">GSMA 测试 CI</string>
@ -169,4 +177,24 @@
<string name="pref_developer_isdr_aid_list">自定义 ISD-R AID 列表</string>
<string name="isdr_aid_list_restore_defaults">重置</string>
<string name="isdr_aid_list">ISD-R AID 列表</string>
<string name="download_wizard_error_iccid_already">此 eSIM 配置文件已存在于您的 eSIM 芯片上。</string>
<string name="download_wizard_error_insufficient_memory">您的 eSIM 芯片没有足够的空间来下载配置文件。</string>
<string name="download_wizard_error_unsupported_profile">您的 eSIM 芯片不支持此 eSIM 配置文件。</string>
<string name="download_wizard_error_card_internal_error">eSIM 芯片错误。</string>
<string name="download_wizard_error_eid_not_supported">您的设备或 eSIM 芯片的 EID 不受您的运营商支持。</string>
<string name="download_wizard_error_eid_mismatch">此 eSIM 配置文件已被下载到另一台设备上。</string>
<string name="download_wizard_error_profile_unreleased">此 eSIM 配置文件已被撤销。</string>
<string name="download_wizard_error_matching_id_refused">激活码无效。</string>
<string name="download_wizard_error_profile_retries_exceeded">已超出 eSIM 配置文件的最大下载尝试次数。</string>
<string name="download_wizard_error_confirmation_code_missing">下载此配置文件需要确认码。</string>
<string name="download_wizard_error_confirmation_code_refused">您输入的确认码无效。</string>
<string name="download_wizard_error_profile_expired">此 eSIM 配置文件已过期。</string>
<string name="download_wizard_error_confirmation_code_retries_exceeded">已超出确认码的最大下载尝试次数。</string>
<string name="download_wizard_error_unknown_hostname">未知的 SM-DP+ 地址</string>
<string name="download_wizard_error_network_unreachable">网络不可达</string>
<string name="download_wizard_error_tls_certificate">TLS 证书错误,不支持此 eSIM 配置文件</string>
<string name="download_wizard_error_suggest_profile_installed">您正在尝试重新安装已下载的 eSIM 配置文件</string>
<string name="download_wizard_error_suggest_insufficient_memory">请删除一些未使用的 eSIM 配置文件,然后重试</string>
<string name="download_wizard_error_suggest_contact_carrier">请联系您的运营商寻求帮助。</string>
<string name="download_wizard_error_suggest_contact_reissue">请联系您的运营商重新签发此 eSIM 配置文件。</string>
</resources>

View file

@ -5,6 +5,7 @@
<string name="euicc_info_unknown">未知</string>
<string name="notification_help">幫助</string>
<string name="profile_reload_slots">重新載入卡槽</string>
<string name="profile_no_enabled_profile">未知</string>
<string name="channel_name_format">虛擬卡槽 %d</string>
<string name="profile_state_enabled">已啟用</string>
<string name="profile_state_disabled">已停用</string>
@ -46,6 +47,7 @@
<string name="profile_download_imei">IMEI (可選)</string>
<string name="profile_download_low_nvram_title">剩餘空間不足</string>
<string name="profile_download_low_nvram_message">目前晶片的剩餘空間不足,可能導致配置下載失敗。\n是否繼續下載</string>
<string name="download_wizard_error_suggest_network_unreachable">請連接到其他網路(例如在 Wi-Fi 和資料之間切換)後重試。</string>
<string name="logs_saved_message">日誌已儲存到指定路徑。需要透過其他 App 分享嗎?</string>
<string name="profile_rename_new_name">新名稱</string>
<string name="profile_rename_encoding_error">無法將名稱編碼為 UTF-8</string>
@ -83,6 +85,7 @@
<string name="pref_advanced_disable_safeguard_removable_esim">允許 停用/刪除 已啟用的設定檔</string>
<string name="pref_advanced_disable_safeguard_removable_esim_desc">預設情況下,此應用程式會阻止您停用可插拔 eSIM 中已啟用的設定檔。\n因為這樣做 <i>有時</i> 會導致無法存取。\n勾選此框以 <i>移除</i> 此保護措施。</string>
<string name="pref_developer_isdr_aid_list_desc">某些品牌的可移除 eUICC 可能會使用自己的非標準 ISD-R AID導致第三方應用程式無法存取。此 App 可以嘗試使用此清單中新增的非標準 AID但不能保證它們一定有效。</string>
<string name="pref_developer_es10x_mss_desc">全局 ES10x MSS</string>
<string name="pref_info">資訊</string>
<string name="pref_info_app_version">App 版本</string>
<string name="pref_info_source_code">原始碼</string>
@ -136,6 +139,7 @@
<string name="euicc_info_sas_accreditation_number">SAS 認證號碼</string>
<string name="euicc_info_pp_version">Protected Profile 版本</string>
<string name="euicc_info_free_nvram">NVRAM 剩餘空間 (eSIM 儲存容量)</string>
<string name="euicc_info_free_nvram_hint">(僅供參考)</string>
<string name="euicc_info_ci_type">證書簽發者 (CI)</string>
<string name="euicc_info_ci_gsma_live">GSMA 生產環境 CI</string>
<string name="euicc_info_ci_gsma_test">GSMA 測試 CI</string>
@ -169,4 +173,24 @@
<string name="pref_developer_isdr_aid_list">自訂 ISD-R AID 列表</string>
<string name="isdr_aid_list_restore_defaults">重置</string>
<string name="isdr_aid_list">ISD-R AID 列表</string>
<string name="download_wizard_error_iccid_already">此 eSIM 設定檔已存在於您的 eSIM 晶片上。</string>
<string name="download_wizard_error_insufficient_memory">您的 eSIM 晶片沒有足夠的空間來下載設定檔。</string>
<string name="download_wizard_error_unsupported_profile">您的 eSIM 晶片不支援此 eSIM 設定檔。</string>
<string name="download_wizard_error_card_internal_error">eSIM 晶片錯誤。</string>
<string name="download_wizard_error_eid_not_supported">您的裝置或 eSIM 晶片的 EID 不受您的電信業者支援。</string>
<string name="download_wizard_error_eid_mismatch">此 eSIM 設定檔已被下載到另一台裝置上。</string>
<string name="download_wizard_error_profile_unreleased">此 eSIM 設定檔已被撤銷。</string>
<string name="download_wizard_error_matching_id_refused">啟用碼無效。</string>
<string name="download_wizard_error_profile_retries_exceeded">已超出 eSIM 設定檔的最大下載嘗試次數。</string>
<string name="download_wizard_error_confirmation_code_missing">下載此設定檔需要確認碼。</string>
<string name="download_wizard_error_confirmation_code_refused">您輸入的確認碼無效。</string>
<string name="download_wizard_error_profile_expired">此 eSIM 設定檔已過期。</string>
<string name="download_wizard_error_confirmation_code_retries_exceeded">已超出確認碼的最大下載嘗試次數。</string>
<string name="download_wizard_error_unknown_hostname">未知的 SM-DP+ 位址</string>
<string name="download_wizard_error_network_unreachable">網路不可達</string>
<string name="download_wizard_error_tls_certificate">TLS 憑證錯誤,不支援此 eSIM 設定檔</string>
<string name="download_wizard_error_suggest_profile_installed">您正在嘗試重新安裝已下載的 eSIM 設定文件</string>
<string name="download_wizard_error_suggest_insufficient_memory">請刪除一些未使用的 eSIM 設定文件,然後重試</string>
<string name="download_wizard_error_suggest_contact_carrier">請聯絡您的電信業者尋求協助。</string>
<string name="download_wizard_error_suggest_contact_reissue">請聯絡您的電信業者重新簽發此 eSIM 設定檔。</string>
</resources>

View file

@ -104,6 +104,27 @@
<string name="download_wizard_diagnostics_last_apdu_exception">Last APDU exception:</string>
<string name="download_wizard_diagnostics_save">Save</string>
<string name="download_wizard_diagnostics_file_template">Diagnostics at %s</string>
<string name="download_wizard_error_iccid_already">This eSIM profile is already present on your eSIM chip.</string>
<string name="download_wizard_error_insufficient_memory">Your eSIM chip does not have sufficient memory left to download the profile.</string>
<string name="download_wizard_error_unsupported_profile">This eSIM profile is unsupported by your eSIM chip.</string>
<string name="download_wizard_error_card_internal_error">An error occurred in your eSIM chip.</string>
<string name="download_wizard_error_eid_not_supported">The EID of your device or eSIM chip is unsupported by your carrier.</string>
<string name="download_wizard_error_eid_mismatch">This eSIM profile has been downloaded on another device.</string>
<string name="download_wizard_error_profile_unreleased">This eSIM profile has been revoked.</string>
<string name="download_wizard_error_matching_id_refused">The activation code is invalid.</string>
<string name="download_wizard_error_profile_retries_exceeded">The maximum number of download attempts for the eSIM profile has been exceeded.</string>
<string name="download_wizard_error_confirmation_code_missing">Confirmation code is required to download this profile.</string>
<string name="download_wizard_error_confirmation_code_refused">The confirmation code you entered is invalid.</string>
<string name="download_wizard_error_profile_expired">This eSIM profile has expired.</string>
<string name="download_wizard_error_confirmation_code_retries_exceeded">The maximum number of download attempts for the confirmation code has been exceeded.</string>
<string name="download_wizard_error_unknown_hostname">Unknown SM-DP+ address</string>
<string name="download_wizard_error_network_unreachable">Network is unreachable</string>
<string name="download_wizard_error_tls_certificate">TLS certificate error, this eSIM profile is not supported</string>
<string name="download_wizard_error_suggest_profile_installed">You are trying to reinstall an already downloaded eSIM profile</string>
<string name="download_wizard_error_suggest_insufficient_memory">Please delete some unused eSIM profiles and try again</string>
<string name="download_wizard_error_suggest_contact_carrier">Please contact your carrier for assistance.</string>
<string name="download_wizard_error_suggest_contact_reissue">Please contact your carrier to reissue this eSIM profile.</string>
<string name="download_wizard_error_suggest_network_unreachable">Please try again after connecting to a different network (e.g. switching between Wi-Fi and data).</string>
<string name="logs_saved_message">Logs have been saved to the selected path. Would you like to share the log through another app?</string>
@ -144,6 +165,7 @@
<string name="euicc_info_sas_accreditation_number">SAS Accreditation Number</string>
<string name="euicc_info_pp_version">Protected Profile Version</string>
<string name="euicc_info_free_nvram">Free NVRAM (eSIM profile storage)</string>
<string name="euicc_info_free_nvram_hint">(for reference only)</string>
<string name="euicc_info_ci_type">Certificate Issuer (CI)</string>
<string name="euicc_info_ci_gsma_live">GSMA Live CI</string>
<string name="euicc_info_ci_gsma_test">GSMA Test CI</string>
@ -200,6 +222,16 @@
<string name="pref_developer_ignore_tls_certificate_desc">Accept any TLS certificate used by the RSP server</string>
<string name="pref_developer_euicc_memory_reset">Allow erasing eUICC</string>
<string name="pref_developer_euicc_memory_reset_desc">This is a dangerous operation and hidden by default. As an alternative, you can delete all profiles manually.</string>
<string name="pref_developer_es10x_mss" translatable="false">ES10x MSS</string>
<string name="pref_developer_es10x_mss_desc">Global ES10x MSS</string>
<string-array name="pref_developer_es10x_entry_keys">
<item>High Efficiency</item>
<item>Most Compatible</item>
</string-array>
<string-array name="pref_developer_es10x_entry_values" translatable="false">
<item>255</item>
<item>63</item>
</string-array>
<string name="pref_developer_isdr_aid_list">Customize ISD-R AID list</string>
<string name="pref_developer_isdr_aid_list_desc">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.</string>
<string name="pref_info">Info</string>

View file

@ -81,6 +81,14 @@
app:summary="@string/pref_developer_euicc_memory_reset_desc"
app:title="@string/pref_developer_euicc_memory_reset" />
<ListPreference
app:iconSpaceReserved="false"
app:key="pref_developer_es10x_mss"
app:summary="@string/pref_developer_es10x_mss_desc"
app:title="@string/pref_developer_es10x_mss"
app:entries="@array/pref_developer_es10x_entry_keys"
app:entryValues="@array/pref_developer_es10x_entry_values" />
<Preference
app:iconSpaceReserved="false"
app:key="pref_developer_isdr_aid_list"

View file

@ -1,20 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="compatibility_check">互換性のチェック</string>
<string name="compatibility_check">互換性の確認</string>
<string name="open_sim_toolkit">SIM ツールキットを開く</string>
<!-- Settings -->
<!-- Toast -->
<string name="toast_ara_m_copied">ARA-M SHA-1 をクリップボードにコピーしました</string>
<string name="toast_prompt_to_enable_sim_toolkit">「%s」アプリを有効化してください</string>
<string name="quick_compatibility">互換性のチェック</string>
<string name="quick_compatibility_compatible">お使いのスマートフォンは %s 対応 SIM カードを管理できます</string>
<string name="quick_compatibility_not_compatible">お使いのスマートフォンは %s と互換性がありません</string>
<string name="quick_compatibility_not_compatible_but_usb">お使いのスマートフォンは %s と完全な互換性がありません。ただし、USBスマートカードリーダーを使用する場合、ほぼすべての機能を利用できます。</string>
<string name="quick_compatibility_result_slots">アクセス可能なスロット: %s</string>
<!-- Quick Compatibility -->
<string name="quick_compatibility">クイックで互換性を確認</string>
<string name="quick_compatibility_compatible">このデバイスで「%s」の対応カードを管理できます</string>
<string name="quick_compatibility_not_compatible">使用しているデバイスは「%s」と互換性がありません</string>
<string name="quick_compatibility_not_compatible_but_usb">使用しているデバイスは「%s」と完全に互換性がありません。ただし、USB スマートカードリーダーを使用すればほぼすべての機能を利用できます。</string>
<string name="quick_compatibility_result_slots">アクセス可能スロット: %s</string>
<string name="quick_compatibility_result_slots_isdr">ISD-R アクセス: %s</string>
<string name="quick_compatibility_result_notes">: これらの結果は参考用です。以上に記載されていない SIM スロットでも、SIM カードを挿入すれば利用できる可能性があります。</string>
<string name="quick_compatibility_result_notes_incompatible">現在 SIM カードが挿入されていない場合は、SIM カードを挿入してから再度互換性チェックをお試しください。どの SIM カードでも構いません。</string>
<string name="quick_compatibility_button_continue">つづく</string>
<string name="quick_compatibility_result_notes">意: これらの結果は参考値です。上記に記載されていない SIM スロットでも、<i>SIM カードを挿入</i>すれば互換性がある場合があります。</string>
<string name="quick_compatibility_result_notes_incompatible">意: 現在 SIM カードが挿入されていない場合は、SIM カードを挿入してから再度互換性の確認をお試しください。どの SIM カードでも構いません。</string>
<string name="quick_compatibility_button_continue">続行</string>
<string name="quick_compatibility_skip">このメッセージを再度表示しない</string>
<string name="quick_compatibility_unknown">不明</string>
</resources>

View file

@ -1,3 +1,4 @@
import com.android.build.gradle.internal.api.ApkVariantOutputImpl
import im.angry.openeuicc.build.*
plugins {
@ -48,4 +49,63 @@ 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<MagiskModuleDirTask>("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<Zip>("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<MagiskModuleDirTask>("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<Zip>("assembleReleaseMagiskModule") {
dependsOn("assembleReleaseMagiskModuleDir")
from((tasks.getByName("assembleReleaseMagiskModuleDir") as MagiskModuleDirTask).outputDir)
archiveFileName = "magisk-release.zip"
destinationDirectory = project.layout.buildDirectory.dir("magisk")
entryCompression = ZipEntryCompression.STORED
}

9
app/magisk/customize.sh Normal file
View file

@ -0,0 +1,9 @@
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

View file

@ -0,0 +1,33 @@
#!/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

1
app/magisk/uninstall.sh Normal file
View file

@ -0,0 +1 @@
pm uninstall "{PKG_NAME}"

View file

@ -42,6 +42,7 @@ class PrivilegedEuiccChannelFactory(context: Context) : DefaultEuiccChannelFacto
isdrAid,
context.preferenceRepository.verboseLoggingFlow,
context.preferenceRepository.ignoreTLSCertificateFlow,
context.preferenceRepository.es10xMssFlow,
)
} catch (_: IllegalArgumentException) {
// Failed

View file

@ -17,6 +17,7 @@
<string name="lui_desc">使用しているデバイスは eSIM をサポートしています。モバイルネットワークに接続するには通信事業者が発行した eSIM をダウンロードするか、物理 SIM を挿入してください。</string>
<string name="lui_skip">スキップ</string>
<string name="lui_download">eSIM をダウンロード</string>
<string name="pref_developer_telephony_manager_removable">TelephonyManagerをどこでも使用</string>
<string name="pref_developer_telephony_manager_removable_desc">デフォルトでは、非特権モード (EasyEUICC) と一致するように、取り外し可能な eUICC に対して OMAPI のみが試行されます。これは、一部のデバイスではうまく機能しない可能性があります。このオプションを選択する場合、取り外し可能な eUICC でも TelephonyManager を使用することになります。</string>
<!-- Preference -->
<string name="pref_developer_telephony_manager_removable">TelephonyManager を常に使用可能にする</string>
<string name="pref_developer_telephony_manager_removable_desc">デフォルトでは、非特権モード (EasyEUICC など) での動作と一致させるためにリムーバブル eUICC に対しては OMAPI のみを試行します。ただし、一部デバイスでは正常に動作しない場合があります。このオプションを選択するとリムーバブル eUICC でも TelephonyManager の使用が強制されます。</string>
</resources>

View file

@ -0,0 +1,74 @@
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<String>
@get:Input
abstract val appName : Property<String>
@get:InputFile
abstract val permsFile : Property<File>
@get:InputFile
abstract val moduleInstaller : Property<File>
@get:Input
abstract val moduleCustomizeScriptText : Property<String>
@get:Input
abstract val moduleUninstallScriptText : Property<String>
@get:Input
abstract val moduleProp : MapProperty<String, String>
@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"))
}
}

View file

@ -11,12 +11,14 @@ 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"
}
@ -74,6 +76,10 @@ 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)
@ -105,23 +111,24 @@ class LocalProfileAssistantImpl(
}
override val profiles: List<LocalProfileInfo>
@Synchronized
get() {
get() = lock.withLock {
val head = LpacJni.es10cGetProfilesInfo(contextHandle)
var curr = head
val ret = mutableListOf<LocalProfileInfo>()
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)
}
@ -130,79 +137,87 @@ class LocalProfileAssistantImpl(
}
override val notifications: List<LocalProfileNotification>
@Synchronized
get() {
get() = lock.withLock {
val head = LpacJni.es10bListNotification(contextHandle)
var curr = head
val ret = mutableListOf<LocalProfileNotification>()
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)
try {
val ret = mutableListOf<LocalProfileNotification>()
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)
}
LpacJni.notificationsFree(head)
return ret.sortedBy { it.seqNumber }.reversed()
}
override val eID: String
@Synchronized
get() = LpacJni.es10cGetEid(contextHandle)!!
get() = lock.withLock { LpacJni.es10cGetEid(contextHandle)!! }
override val euiccInfo2: EuiccInfo2?
@Synchronized
get() {
get() = lock.withLock {
val cInfo = LpacJni.es10cexGetEuiccInfo2(contextHandle)
if (cInfo == 0L) return null
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
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)
}
}
@Synchronized
override fun enableProfile(iccid: String, refresh: Boolean): Boolean =
override fun enableProfile(iccid: String, refresh: Boolean): Boolean = lock.withLock {
LpacJni.es10cEnableProfile(contextHandle, iccid, refresh) == 0
}
@Synchronized
override fun disableProfile(iccid: String, refresh: Boolean): Boolean =
override fun disableProfile(iccid: String, refresh: Boolean): Boolean = lock.withLock {
LpacJni.es10cDisableProfile(contextHandle, iccid, refresh) == 0
}
@Synchronized
override fun deleteProfile(iccid: String): Boolean =
override fun deleteProfile(iccid: String): Boolean = lock.withLock {
LpacJni.es10cDeleteProfile(contextHandle, iccid) == 0
}
@Synchronized
override fun downloadProfile(smdp: String, matchingId: String?, imei: String?,
confirmationCode: String?, callback: ProfileDownloadCallback) {
override fun downloadProfile(
smdp: String, matchingId: String?, imei: String?,
confirmationCode: String?, callback: ProfileDownloadCallback
) = lock.withLock {
val res = LpacJni.downloadProfile(
contextHandle,
smdp,
@ -229,18 +244,17 @@ class LocalProfileAssistantImpl(
}
}
@Synchronized
override fun deleteNotification(seqNumber: Long): Boolean =
override fun deleteNotification(seqNumber: Long): Boolean = lock.withLock {
LpacJni.es10bDeleteNotification(contextHandle, seqNumber) == 0
}
@Synchronized
override fun handleNotification(seqNumber: Long): Boolean =
override fun handleNotification(seqNumber: Long): Boolean = lock.withLock {
LpacJni.handleNotification(contextHandle, seqNumber).also {
Log.d(TAG, "handleNotification $seqNumber = $it")
} == 0
}
@Synchronized
override fun setNickname(iccid: String, nickname: String) {
override fun setNickname(iccid: String, nickname: String) = lock.withLock {
val encoded = try {
Charsets.UTF_8.encode(nickname).array()
} catch (e: CharacterCodingException) {
@ -259,11 +273,12 @@ class LocalProfileAssistantImpl(
}
override fun euiccMemoryReset() {
LpacJni.es10cEuiccMemoryReset(contextHandle)
lock.withLock {
LpacJni.es10cEuiccMemoryReset(contextHandle)
}
}
@Synchronized
override fun close() {
override fun close() = lock.withLock {
if (!finalized) {
LpacJni.euiccFini(contextHandle)
LpacJni.destroyContext(contextHandle)