forked from PeterCxy/OpenEUICC
Compare commits
25 commits
Author | SHA1 | Date | |
---|---|---|---|
7c2157daa4 | |||
e604f39e0d | |||
797f3dc722 | |||
1be6c25cb6 | |||
c0cd7a25cf | |||
dfdd8948c5 | |||
1fd13634fc | |||
29b2cba673 | |||
8c982ae890 | |||
eb006b8f19 | |||
0506475a85 | |||
0b4d065d4b | |||
52cf68d43d | |||
4c221f74ff | |||
4991f2580e | |||
65775c1d06 | |||
cbe3d1fd50 | |||
677b69cedf | |||
6d43a9207c | |||
f056e2d313 | |||
4ac0820bbf | |||
c0dc8ac19d | |||
21c04ed179 | |||
db4645b17f | |||
149a19ca1c |
50 changed files with 541 additions and 711 deletions
|
@ -1,7 +1,7 @@
|
|||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
build-debug:
|
||||
|
|
2
.gitmodules
vendored
2
.gitmodules
vendored
|
@ -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/lpac
|
||||
url = https://github.com/estkme-group/lpac.git
|
||||
|
|
|
@ -45,8 +45,9 @@
|
|||
<!-- Accepts URIs that begin with "lpa:" -->
|
||||
<!-- for example: "LPA:1$..." -->
|
||||
<!-- refs: https://www.iana.org/assignments/uri-schemes/prov/lpa -->
|
||||
<data android:scheme="lpa"/>
|
||||
<data android:sspPrefix="1$"/>
|
||||
<data android:scheme="lpa" />
|
||||
<data android:scheme="LPA" tools:ignore="AppLinkUrlError" />
|
||||
<data android:sspPrefix="1$" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccCha
|
|||
)
|
||||
try {
|
||||
return EuiccChannelImpl(
|
||||
context.getString(R.string.omapi),
|
||||
context.getString(R.string.channel_type_omapi),
|
||||
port,
|
||||
intrinsicChannelName = null,
|
||||
OmapiApduInterface(
|
||||
|
@ -69,7 +69,7 @@ open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccCha
|
|||
): EuiccChannel? {
|
||||
try {
|
||||
return EuiccChannelImpl(
|
||||
context.getString(R.string.usb),
|
||||
context.getString(R.string.channel_type_usb),
|
||||
FakeUiccPortInfoCompat(FakeUiccCardInfoCompat(EuiccChannelManager.USB_CHANNEL_ID)),
|
||||
intrinsicChannelName = ccidCtx.productName,
|
||||
UsbApduInterface(
|
||||
|
|
|
@ -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.enable_disable_timeout)
|
||||
get() = context.getString(R.string.profile_switch_timeout)
|
||||
|
||||
override fun formatInternalChannelName(logicalSlotId: Int): String =
|
||||
context.getString(R.string.channel_name_format, logicalSlotId)
|
||||
|
|
|
@ -27,9 +27,16 @@ 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: <https://regex101.com/r/5FFz8u>
|
||||
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.yes, R.string.no)
|
||||
private val YES_NO = Pair(R.string.euicc_info_yes, R.string.euicc_info_no)
|
||||
}
|
||||
|
||||
private lateinit var swipeRefresh: SwipeRefreshLayout
|
||||
|
@ -62,7 +69,7 @@ class EuiccInfoActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
|||
logicalSlotId = intent.getIntExtra("logicalSlotId", 0)
|
||||
|
||||
val channelTitle = if (logicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) {
|
||||
getString(R.string.usb)
|
||||
getString(R.string.channel_type_usb)
|
||||
} else {
|
||||
appContainer.customizableTextProvider.formatInternalChannelName(logicalSlotId)
|
||||
}
|
||||
|
@ -109,13 +116,14 @@ class EuiccInfoActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
|||
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_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?.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())) }
|
||||
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)
|
||||
|
@ -123,25 +131,20 @@ 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.unknown // the case is not mp, but it's is not common
|
||||
signers.isEmpty() -> R.string.euicc_info_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.information_unavailable)
|
||||
val atr = channel.atr?.encodeHex() ?: getString(R.string.euicc_info_unavailable)
|
||||
add(Item(R.string.euicc_info_atr, atr, copiedToastResId = R.string.toast_atr_copied))
|
||||
}
|
||||
|
||||
@Suppress("SameParameterValue")
|
||||
private fun formatByBoolean(b: Boolean, res: Pair<Int, Int>): 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)
|
||||
|
@ -168,7 +171,7 @@ class EuiccInfoActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
|||
fun bind(item: Item) {
|
||||
copiedToastResId = item.copiedToastResId
|
||||
title.setText(item.titleResId)
|
||||
content.text = item.content ?: getString(R.string.unknown)
|
||||
content.text = item.content ?: getString(R.string.euicc_info_unknown)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -253,7 +253,7 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
|
|||
if (!isUsb) {
|
||||
withContext(Dispatchers.Main) {
|
||||
AlertDialog.Builder(requireContext()).apply {
|
||||
setMessage(R.string.switch_did_not_refresh)
|
||||
setMessage(R.string.profile_switch_did_not_refresh)
|
||||
setPositiveButton(android.R.string.ok) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
requireActivity().finish()
|
||||
|
@ -347,6 +347,7 @@ 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 {
|
||||
|
@ -366,7 +367,9 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
|
|||
true
|
||||
}
|
||||
|
||||
profileMenu.setOnClickListener { showOptionsMenu() }
|
||||
profileMenu.setOnClickListener {
|
||||
showOptionsMenu()
|
||||
}
|
||||
}
|
||||
|
||||
private lateinit var profile: LocalProfileInfo
|
||||
|
@ -377,9 +380,9 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
|
|||
|
||||
state.setText(
|
||||
if (profile.isEnabled) {
|
||||
R.string.enabled
|
||||
R.string.profile_state_enabled
|
||||
} else {
|
||||
R.string.disabled
|
||||
R.string.profile_state_disabled
|
||||
}
|
||||
)
|
||||
provider.text = profile.providerName
|
||||
|
@ -396,6 +399,13 @@ 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
|
||||
|
@ -461,6 +471,7 @@ 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])
|
||||
|
|
|
@ -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.usb)
|
||||
val productName = it.productName ?: getString(R.string.channel_type_usb)
|
||||
newPages.add(Page(EuiccChannelManager.USB_CHANNEL_ID, productName) {
|
||||
UsbCcidReaderFragment()
|
||||
})
|
||||
|
|
|
@ -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.usb)
|
||||
getString(R.string.channel_type_usb)
|
||||
} else {
|
||||
appContainer.customizableTextProvider.formatInternalChannelName(logicalSlotId)
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment
|
|||
super.onViewCreated(view, savedInstanceState)
|
||||
profileRenameNewName.editText!!.setText(currentName)
|
||||
toolbar.apply {
|
||||
setTitle(R.string.rename)
|
||||
setTitle(R.string.profile_rename)
|
||||
setNavigationOnClickListener {
|
||||
if (!renaming) dismiss()
|
||||
}
|
||||
|
|
|
@ -123,8 +123,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
|
||||
if (uri?.scheme == "lpa") {
|
||||
val uri = intent.data ?: return
|
||||
if (uri.scheme.contentEquals("lpa", ignoreCase = true)) {
|
||||
val parsed = LPAString.parse(uri.schemeSpecificPart)
|
||||
state.smdp = parsed.address
|
||||
state.matchingId = parsed.matchingId
|
||||
|
|
|
@ -8,6 +8,7 @@ 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() {
|
||||
|
@ -86,9 +87,10 @@ class DownloadWizardDiagnosticsFragment : DownloadWizardActivity.DownloadWizardS
|
|||
ret.appendLine()
|
||||
|
||||
val str = resp.data.decodeToString(throwOnInvalidSequence = false)
|
||||
|
||||
ret.appendLine(
|
||||
if (str.startsWith('{')) {
|
||||
str.prettyPrintJson()
|
||||
JSONObject(str).toString(2)
|
||||
} else {
|
||||
str
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ 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 {
|
||||
|
@ -187,12 +186,12 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt
|
|||
}
|
||||
|
||||
title.text = if (item.logicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) {
|
||||
item.intrinsicChannelName ?: root.context.getString(R.string.usb)
|
||||
item.intrinsicChannelName ?: root.context.getString(R.string.channel_type_usb)
|
||||
} else {
|
||||
appContainer.customizableTextProvider.formatInternalChannelName(item.logicalSlotId)
|
||||
}
|
||||
eID.text = item.eID
|
||||
activeProfile.text = item.enabledProfileName ?: root.context.getString(R.string.unknown)
|
||||
activeProfile.text = item.enabledProfileName ?: root.context.getString(R.string.profile_no_enabled_profile)
|
||||
freeSpace.text = formatFreeSpace(item.freeSpace)
|
||||
checkBox.isChecked = adapter.currentSelectedIdx == idx
|
||||
}
|
||||
|
|
|
@ -41,73 +41,3 @@ fun parseIsdrAidList(s: String): List<ByteArray> =
|
|||
.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<Char>()
|
||||
|
||||
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()
|
||||
}
|
|
@ -102,8 +102,8 @@ fun <T : ActivityResultCaller> T.setupLogSaving(
|
|||
|
||||
AlertDialog.Builder(context).apply {
|
||||
setMessage(R.string.logs_saved_message)
|
||||
setNegativeButton(R.string.no) { _, _ -> }
|
||||
setPositiveButton(R.string.yes) { _, _ ->
|
||||
setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
val intent = Intent(Intent.ACTION_SEND).apply {
|
||||
type = "text/plain"
|
||||
clipData = ClipData.newUri(context.contentResolver, lastFileName, uri)
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/provider_label"
|
||||
android:text="@string/provider"
|
||||
android:text="@string/profile_provider"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
|
@ -106,7 +106,7 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/iccid_label"
|
||||
android:text="@string/iccid"
|
||||
android:text="@string/profile_iccid"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
|
@ -129,6 +129,14 @@
|
|||
app:layout_constraintTop_toBottomOf="@id/profile_class"
|
||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/profile_sequence_number"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="6dp"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/iccid" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
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"/>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
<item
|
||||
android:id="@+id/reset"
|
||||
android:title="@string/reset"
|
||||
android:title="@string/isdr_aid_list_restore_defaults"
|
||||
android:icon="@drawable/ic_refresh_black"
|
||||
app:showAsAction="ifRoom" />
|
||||
</menu>
|
|
@ -3,7 +3,7 @@
|
|||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/reload"
|
||||
android:title="@string/reload"
|
||||
android:title="@string/profile_reload_slots"
|
||||
android:icon="@drawable/ic_refresh_black"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
|
|
|
@ -4,6 +4,6 @@
|
|||
<item
|
||||
android:id="@+id/help"
|
||||
android:icon="@drawable/ic_help_black"
|
||||
android:title="@string/help"
|
||||
android:title="@string/notification_help"
|
||||
app:showAsAction="always" />
|
||||
</menu>
|
|
@ -4,6 +4,6 @@
|
|||
<item
|
||||
android:id="@+id/ok"
|
||||
android:icon="@drawable/ic_check_black"
|
||||
android:title="@string/rename"
|
||||
android:title="@string/profile_rename"
|
||||
app:showAsAction="ifRoom"/>
|
||||
</menu>
|
|
@ -2,18 +2,18 @@
|
|||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/enable"
|
||||
android:title="@string/enable"/>
|
||||
android:title="@string/profile_enable"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/disable"
|
||||
android:visible="false"
|
||||
android:title="@string/disable"/>
|
||||
android:title="@string/profile_disable"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/rename"
|
||||
android:title="@string/rename"/>
|
||||
android:title="@string/profile_rename"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/delete"
|
||||
android:title="@string/delete"/>
|
||||
android:title="@string/profile_delete"/>
|
||||
</menu>
|
|
@ -2,24 +2,24 @@
|
|||
<resources>
|
||||
<string name="no_euicc">このアプリでアクセスできるリムーバブル eUICC カードがデバイス上で検出されていません。互換性のあるカード挿入または USB リーダーを接続してください。</string>
|
||||
<string name="no_profile">この eSIM にはプロファイルがありません。</string>
|
||||
<string name="unknown">不明</string>
|
||||
<string name="information_unavailable">情報がありません</string>
|
||||
<string name="help">ヘルプ</string>
|
||||
<string name="reload">スロットを再読み込み</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="channel_name_format">論理スロット %d</string>
|
||||
<string name="enabled">有効済み</string>
|
||||
<string name="disabled">無効済み</string>
|
||||
<string name="provider">プロバイダー:</string>
|
||||
<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>
|
||||
<string name="profile_class_provisioning">プロビジョニング</string>
|
||||
<string name="profile_class_operational">稼働中</string>
|
||||
<string name="enable">有効化</string>
|
||||
<string name="disable">無効化</string>
|
||||
<string name="delete">削除</string>
|
||||
<string name="rename">名前を変更</string>
|
||||
<string name="enable_disable_timeout">eSIM チップがプロファイルの切り替えの待機中にタイムアウトしました。これはデバイスのモデムファームウェアのバグの可能性があります。機内モードに切り替えるかアプリを再起動、デバイスを再起動してください。</string>
|
||||
<string name="switch_did_not_refresh">操作は成功しましたが、デバイスのモデムが更新を拒否しました。新しいプロファイルを使用するには機内モードに切り替えるか、再起動する必要があります。</string>
|
||||
<string name="profile_enable">有効化</string>
|
||||
<string name="profile_disable">無効化</string>
|
||||
<string name="profile_delete">削除</string>
|
||||
<string name="profile_rename">名前を変更</string>
|
||||
<string name="profile_switch_timeout">eSIM チップがプロファイルの切り替えの待機中にタイムアウトしました。これはデバイスのモデムファームウェアのバグの可能性があります。機内モードに切り替えるかアプリを再起動、デバイスを再起動してください。</string>
|
||||
<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_iccid_copied">ICCID をクリップボードにコピーしました</string>
|
||||
|
@ -110,7 +110,7 @@
|
|||
<string name="euicc_info_fw_ver">製品ファームウェアバージョン</string>
|
||||
<string name="euicc_info_sgp22_version">SGP.22 バージョン</string>
|
||||
<string name="euicc_info_firmware_version">eUICC OS バージョン</string>
|
||||
<string name="euicc_info_globalplatform_version">グローバルプラットフォームのバージョン</string>
|
||||
<string name="euicc_info_gp_version">グローバルプラットフォームのバージョン</string>
|
||||
<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>
|
||||
|
@ -118,8 +118,8 @@
|
|||
<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="yes">はい</string>
|
||||
<string name="no">いいえ</string>
|
||||
<string name="euicc_info_yes">はい</string>
|
||||
<string name="euicc_info_no">いいえ</string>
|
||||
<string name="logs_save">保存</string>
|
||||
<string name="logs_filename_template">%s のログ</string>
|
||||
<string name="developer_options_steps">開発者になるまであと %d ステップです。</string>
|
||||
|
@ -167,6 +167,6 @@
|
|||
<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="reset">リセット</string>
|
||||
<string name="isdr_aid_list_restore_defaults">リセット</string>
|
||||
<string name="isdr_aid_list">ISD-R AID リスト</string>
|
||||
</resources>
|
||||
|
|
|
@ -2,20 +2,20 @@
|
|||
<resources>
|
||||
<string name="no_euicc">在此设备上未检测到此应用程序可访问的可插拔 eUICC 卡。请插入兼容卡或 USB 读卡器。</string>
|
||||
<string name="no_profile">此 eSIM 上还没有配置文件</string>
|
||||
<string name="unknown">未知</string>
|
||||
<string name="help">帮助</string>
|
||||
<string name="reload">重新加载卡槽</string>
|
||||
<string name="euicc_info_unknown">未知</string>
|
||||
<string name="notification_help">帮助</string>
|
||||
<string name="profile_reload_slots">重新加载卡槽</string>
|
||||
<string name="channel_name_format">逻辑卡槽 %d</string>
|
||||
<string name="enabled">已启用</string>
|
||||
<string name="disabled">已禁用</string>
|
||||
<string name="provider">提供商:</string>
|
||||
<string name="profile_state_enabled">已启用</string>
|
||||
<string name="profile_state_disabled">已禁用</string>
|
||||
<string name="profile_provider">提供商:</string>
|
||||
<string name="profile_class">类型:</string>
|
||||
<string name="enable">启用</string>
|
||||
<string name="disable">禁用</string>
|
||||
<string name="delete">删除</string>
|
||||
<string name="rename">重命名</string>
|
||||
<string name="enable_disable_timeout">等待 eSIM 芯片切换配置文件时超时。这可能是您手机基带固件中的一个错误。请尝试切换飞行模式、重新启动应用程序或重新启动手机</string>
|
||||
<string name="switch_did_not_refresh">操作成功, 但是您手机的基带拒绝刷新。您可能需要切换飞行模式或重新启动,以便使用新的配置文件。</string>
|
||||
<string name="profile_enable">启用</string>
|
||||
<string name="profile_disable">禁用</string>
|
||||
<string name="profile_delete">删除</string>
|
||||
<string name="profile_rename">重命名</string>
|
||||
<string name="profile_switch_timeout">等待 eSIM 芯片切换配置文件时超时。这可能是您手机基带固件中的一个错误。请尝试切换飞行模式、重新启动应用程序或重新启动手机</string>
|
||||
<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_iccid_copied">已复制 ICCID 到剪贴板</string>
|
||||
|
@ -132,7 +132,7 @@
|
|||
<string name="euicc_info_removable">可插拔</string>
|
||||
<string name="euicc_info_sgp22_version">SGP.22 版本</string>
|
||||
<string name="euicc_info_firmware_version">eUICC OS 版本</string>
|
||||
<string name="euicc_info_globalplatform_version">GlobalPlatform 版本</string>
|
||||
<string name="euicc_info_gp_version">GlobalPlatform 版本</string>
|
||||
<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>
|
||||
|
@ -140,8 +140,8 @@
|
|||
<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="yes">是</string>
|
||||
<string name="no">否</string>
|
||||
<string name="euicc_info_yes">是</string>
|
||||
<string name="euicc_info_no">否</string>
|
||||
<string name="developer_options_steps">还有 %d 步成为开发者</string>
|
||||
<string name="developer_options_enabled">你现在是开发者了!</string>
|
||||
<string name="pref_advanced_language">语言</string>
|
||||
|
@ -152,7 +152,7 @@
|
|||
<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 服务器使用任意证书</string>
|
||||
<string name="information_unavailable">无信息</string>
|
||||
<string name="euicc_info_unavailable">无信息</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>
|
||||
|
@ -167,6 +167,6 @@
|
|||
<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="reset">重置</string>
|
||||
<string name="isdr_aid_list_restore_defaults">重置</string>
|
||||
<string name="isdr_aid_list">ISD-R AID 列表</string>
|
||||
</resources>
|
|
@ -2,20 +2,20 @@
|
|||
<resources>
|
||||
<string name="no_euicc">在此裝置上未檢測到此應用程式可訪問的可插拔 eUICC 卡。請插入相容卡或 USB 晶片讀卡機。</string>
|
||||
<string name="no_profile">此 eSIM 上還沒有設定檔</string>
|
||||
<string name="unknown">未知</string>
|
||||
<string name="help">幫助</string>
|
||||
<string name="reload">重新載入卡槽</string>
|
||||
<string name="euicc_info_unknown">未知</string>
|
||||
<string name="notification_help">幫助</string>
|
||||
<string name="profile_reload_slots">重新載入卡槽</string>
|
||||
<string name="channel_name_format">虛擬卡槽 %d</string>
|
||||
<string name="enabled">已啟用</string>
|
||||
<string name="disabled">已停用</string>
|
||||
<string name="provider">電信業者:</string>
|
||||
<string name="profile_state_enabled">已啟用</string>
|
||||
<string name="profile_state_disabled">已停用</string>
|
||||
<string name="profile_provider">電信業者:</string>
|
||||
<string name="profile_class">類型:</string>
|
||||
<string name="enable">啟用</string>
|
||||
<string name="disable">停用</string>
|
||||
<string name="delete">刪除</string>
|
||||
<string name="rename">重新命名</string>
|
||||
<string name="enable_disable_timeout">等待 eSIM 切換設定檔時逾時。這可能是您手機基頻處理器韌體中的一個錯誤。請嘗試切換飛航模式、重新啟動應用程式或重新啟動手機</string>
|
||||
<string name="switch_did_not_refresh">操作成功, 但是您手機的基頻處理器沒有重新整理。您可能需要切換飛航模式或重新啟動,以便使用新的設定檔。</string>
|
||||
<string name="profile_enable">啟用</string>
|
||||
<string name="profile_disable">停用</string>
|
||||
<string name="profile_delete">刪除</string>
|
||||
<string name="profile_rename">重新命名</string>
|
||||
<string name="profile_switch_timeout">等待 eSIM 切換設定檔時逾時。這可能是您手機基頻處理器韌體中的一個錯誤。請嘗試切換飛航模式、重新啟動應用程式或重新啟動手機</string>
|
||||
<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_iccid_copied">已複製 ICCID 到剪貼簿</string>
|
||||
|
@ -132,7 +132,7 @@
|
|||
<string name="euicc_info_removable">可插拔</string>
|
||||
<string name="euicc_info_sgp22_version">SGP.22 版本</string>
|
||||
<string name="euicc_info_firmware_version">eUICC OS 版本</string>
|
||||
<string name="euicc_info_globalplatform_version">GlobalPlatform 版本</string>
|
||||
<string name="euicc_info_gp_version">GlobalPlatform 版本</string>
|
||||
<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>
|
||||
|
@ -140,8 +140,8 @@
|
|||
<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="yes">是</string>
|
||||
<string name="no">否</string>
|
||||
<string name="euicc_info_yes">是</string>
|
||||
<string name="euicc_info_no">否</string>
|
||||
<string name="developer_options_steps">還有 %d 步成為開發者</string>
|
||||
<string name="developer_options_enabled">您現在是開發者了!</string>
|
||||
<string name="pref_advanced_language">語言</string>
|
||||
|
@ -152,7 +152,7 @@
|
|||
<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 伺服器使用任意證書</string>
|
||||
<string name="information_unavailable">無資訊</string>
|
||||
<string name="euicc_info_unavailable">無資訊</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>
|
||||
|
@ -167,6 +167,6 @@
|
|||
<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="reset">重置</string>
|
||||
<string name="isdr_aid_list_restore_defaults">重置</string>
|
||||
<string name="isdr_aid_list">ISD-R AID 列表</string>
|
||||
</resources>
|
|
@ -2,31 +2,34 @@
|
|||
<resources>
|
||||
<string name="no_euicc">No removable eUICC card accessible by this app is detected on this device. Insert a compatible card or a USB reader.</string>
|
||||
<string name="no_profile">No profiles (yet) on this eSIM.</string>
|
||||
<string name="unknown">Unknown</string>
|
||||
<string name="information_unavailable">Information Unavailable</string>
|
||||
<string name="help">Help</string>
|
||||
<string name="reload">Reload Slots</string>
|
||||
|
||||
<string name="notification_help">Help</string>
|
||||
|
||||
<string name="profile_reload_slots">Reload Slots</string>
|
||||
<string name="profile_no_enabled_profile">Unknown</string>
|
||||
|
||||
<string name="channel_name_format">Logical Slot %d</string>
|
||||
<string name="usb" translatable="false">USB</string>
|
||||
<string name="omapi" translatable="false">OpenMobile API (OMAPI)</string>
|
||||
<string name="channel_type_usb" translatable="false">USB</string>
|
||||
<string name="channel_type_omapi" translatable="false">OpenMobile API (OMAPI)</string>
|
||||
|
||||
<string name="enabled">Enabled</string>
|
||||
<string name="disabled">Disabled</string>
|
||||
<string name="provider">Provider:</string>
|
||||
<!-- Profile -->
|
||||
<string name="profile_state_enabled">Enabled</string>
|
||||
<string name="profile_state_disabled">Disabled</string>
|
||||
<string name="profile_provider">Provider:</string>
|
||||
<string name="profile_class">Class:</string>
|
||||
<string name="profile_class_testing">Testing</string>
|
||||
<string name="profile_class_provisioning">Provisioning</string>
|
||||
<string name="profile_class_operational">Operational</string>
|
||||
<string name="iccid" translatable="false">ICCID:</string>
|
||||
<string name="profile_iccid" translatable="false">ICCID:</string>
|
||||
<string name="profile_sequence_number_format" translatable="false">#%d</string>
|
||||
|
||||
<string name="enable">Enable</string>
|
||||
<string name="disable">Disable</string>
|
||||
<string name="delete">Delete</string>
|
||||
<string name="rename">Rename</string>
|
||||
<string name="profile_enable">Enable</string>
|
||||
<string name="profile_disable">Disable</string>
|
||||
<string name="profile_delete">Delete</string>
|
||||
<string name="profile_rename">Rename</string>
|
||||
|
||||
<string name="enable_disable_timeout">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.</string>
|
||||
<string name="switch_did_not_refresh">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.</string>
|
||||
<string name="profile_switch_timeout">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.</string>
|
||||
<string name="profile_switch_did_not_refresh">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.</string>
|
||||
|
||||
<string name="toast_profile_enable_failed">Cannot switch to new eSIM profile.</string>
|
||||
<string name="toast_profile_delete_confirm_text_mismatched">Confirmation string mismatch</string>
|
||||
|
@ -137,7 +140,7 @@
|
|||
<string name="euicc_info_isdr_aid" translatable="false">ISD-R AID</string>
|
||||
<string name="euicc_info_sgp22_version">SGP.22 Version</string>
|
||||
<string name="euicc_info_firmware_version">eUICC OS Version</string>
|
||||
<string name="euicc_info_globalplatform_version">GlobalPlatform Version</string>
|
||||
<string name="euicc_info_gp_version">GlobalPlatform Version</string>
|
||||
<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>
|
||||
|
@ -154,8 +157,11 @@
|
|||
<string name="euicc_memory_reset_confirm_text">I CONFIRM TO ERASE THE CHIP WHOSE EID ENDS WITH %s AND UNDERSTAND THAT THIS IS IRREVERSIBLE</string>
|
||||
<string name="euicc_memory_reset_invoke_button">Erase</string>
|
||||
|
||||
<string name="yes">Yes</string>
|
||||
<string name="no">No</string>
|
||||
<!-- eUICC Info -->
|
||||
<string name="euicc_info_yes">Yes</string>
|
||||
<string name="euicc_info_no">No</string>
|
||||
<string name="euicc_info_unknown">Unknown</string>
|
||||
<string name="euicc_info_unavailable">Information Unavailable</string>
|
||||
|
||||
<string name="logs_save">Save</string>
|
||||
<string name="logs_filename_template">Logs at %s</string>
|
||||
|
@ -163,10 +169,9 @@
|
|||
<string name="developer_options_steps">You are %d steps away from being a developer.</string>
|
||||
<string name="developer_options_enabled">You are now a developer!</string>
|
||||
|
||||
<string name="reset">Reset</string>
|
||||
|
||||
<string name="isdr_aid_list">ISD-R AID List</string>
|
||||
<string name="isdr_aid_list_saved">Saved custom ISD-R AID list.</string>
|
||||
<string name="isdr_aid_list_restore_defaults">Reset</string>
|
||||
|
||||
<string name="pref_settings">Settings</string>
|
||||
<string name="pref_notifications">Notifications</string>
|
||||
|
|
|
@ -24,9 +24,9 @@
|
|||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="im.angry.openeuicc.ui.CompatibilityCheckActivity"
|
||||
android:name="im.angry.openeuicc.ui.QuickCompatibilityActivity"
|
||||
android:exported="false"
|
||||
android:label="@string/compatibility_check" />
|
||||
android:label="@string/quick_compatibility" />
|
||||
|
||||
<service
|
||||
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package im.angry.openeuicc.di
|
||||
|
||||
import android.content.Context
|
||||
import im.angry.openeuicc.util.UnprivilegedPreferenceRepository
|
||||
|
||||
class UnprivilegedAppContainer(context: Context) : DefaultAppContainer(context) {
|
||||
override val uiComponentFactory by lazy {
|
||||
|
@ -10,4 +11,8 @@ class UnprivilegedAppContainer(context: Context) : DefaultAppContainer(context)
|
|||
override val customizableTextProvider by lazy {
|
||||
UnprivilegedCustomizableTextProvider(context)
|
||||
}
|
||||
|
||||
override val preferenceRepository by lazy {
|
||||
UnprivilegedPreferenceRepository(context)
|
||||
}
|
||||
}
|
|
@ -2,12 +2,12 @@ package im.angry.openeuicc.di
|
|||
|
||||
import androidx.fragment.app.Fragment
|
||||
import im.angry.openeuicc.ui.EuiccManagementFragment
|
||||
import im.angry.openeuicc.ui.SettingsFragment
|
||||
import im.angry.openeuicc.ui.QuickCompatibilityFragment
|
||||
import im.angry.openeuicc.ui.UnprivilegedEuiccManagementFragment
|
||||
import im.angry.openeuicc.ui.UnprivilegedNoEuiccPlaceholderFragment
|
||||
import im.angry.openeuicc.ui.UnprivilegedSettingsFragment
|
||||
|
||||
class UnprivilegedUiComponentFactory : DefaultUiComponentFactory() {
|
||||
open class UnprivilegedUiComponentFactory : DefaultUiComponentFactory() {
|
||||
override fun createEuiccManagementFragment(slotId: Int, portId: Int): EuiccManagementFragment =
|
||||
UnprivilegedEuiccManagementFragment.newInstance(slotId, portId)
|
||||
|
||||
|
@ -16,4 +16,7 @@ class UnprivilegedUiComponentFactory : DefaultUiComponentFactory() {
|
|||
|
||||
override fun createSettingsFragment(): Fragment =
|
||||
UnprivilegedSettingsFragment()
|
||||
|
||||
open fun createQuickCompatibilityFragment(): Fragment =
|
||||
QuickCompatibilityFragment()
|
||||
}
|
|
@ -1,95 +0,0 @@
|
|||
package im.angry.openeuicc.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
import android.text.Html
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.children
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import im.angry.easyeuicc.R
|
||||
import im.angry.openeuicc.util.*
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class CompatibilityCheckActivity: AppCompatActivity() {
|
||||
private lateinit var compatibilityCheckList: RecyclerView
|
||||
private val compatibilityChecks: List<CompatibilityCheck> 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<RecyclerView>(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<View>(viewId).isVisible = true
|
||||
}
|
||||
}
|
||||
|
||||
inner class CompatibilityChecksAdapter: RecyclerView.Adapter<ViewHolder>() {
|
||||
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])
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
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()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,186 @@
|
|||
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<String> = emptyList(),
|
||||
val slotsIsdr: List<String> = 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<TextView>(R.id.quick_compatibility_device_information)
|
||||
.text = formatDeviceInformation()
|
||||
requireViewById<Button>(R.id.quick_compatibility_button_continue)
|
||||
.setOnClickListener { onContinueToApp() }
|
||||
// Can't use the lazy field yet
|
||||
requireViewById<CheckBox>(R.id.quick_compatibility_skip).setOnCheckedChangeListener { compoundButton, b ->
|
||||
if (compoundButton.isVisible) {
|
||||
runBlocking {
|
||||
preferenceRepository.skipQuickCompatibilityFlow
|
||||
.updatePreference(b)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
lifecycleScope.launch {
|
||||
onCompatibilityUpdate(withContext(Dispatchers.IO) {
|
||||
getCompatibilityCheckResult()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun onContinueToApp() {
|
||||
requireActivity().finish()
|
||||
}
|
||||
|
||||
private fun onCompatibilityUpdate(result: CompatibilityResult) {
|
||||
conclusion.text = formatConclusion(result)
|
||||
if (result.compatibility == Compatibility.COMPATIBLE) {
|
||||
// Don't show the message again, ever, if the result is compatible
|
||||
runBlocking {
|
||||
preferenceRepository.skipQuickCompatibilityFlow
|
||||
.updatePreference(true)
|
||||
}
|
||||
resultSlots.isVisible = true
|
||||
resultSlots.text = getString(
|
||||
R.string.quick_compatibility_result_slots,
|
||||
ListFormatter.getInstance().format(result.slotsOmapi)
|
||||
)
|
||||
resultSlotsIsdr.isVisible = true
|
||||
resultSlotsIsdr.text =
|
||||
getString(
|
||||
R.string.quick_compatibility_result_slots_isdr,
|
||||
if (result.slotsIsdr.isEmpty()) {
|
||||
getString(R.string.quick_compatibility_unknown)
|
||||
} else {
|
||||
ListFormatter.getInstance().format(result.slotsIsdr)
|
||||
}
|
||||
)
|
||||
resultNotes.isVisible = true
|
||||
} else {
|
||||
resultNotes.isVisible = true
|
||||
resultNotes.text = getString(R.string.quick_compatibility_result_notes_incompatible)
|
||||
skipCheckBox.isVisible = true
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getCompatibilityCheckResult(): CompatibilityResult {
|
||||
val service = connectSEService(requireContext())
|
||||
if (!service.isConnected) {
|
||||
return CompatibilityResult(Compatibility.NOT_COMPATIBLE)
|
||||
}
|
||||
val readers = service.readers.filter(Reader::isSIM)
|
||||
val omapiSlots = readers.mapNotNull(Reader::slotIndex)
|
||||
val slots = readers.mapNotNull { reader ->
|
||||
try {
|
||||
// Note: we ONLY check the default ISD-R AID, because this test is for the _device_,
|
||||
// NOT the eUICC. We don't care what AID a potential eUICC might use, all we need to
|
||||
// check is we can open _some_ AID.
|
||||
reader.openSession().openLogicalChannel(EUICC_DEFAULT_ISDR_AID.decodeHex())?.close()
|
||||
reader.slotIndex
|
||||
} catch (_: SecurityException) {
|
||||
// Ignore; this is expected when everything works
|
||||
// ref: https://android.googlesource.com/platform/frameworks/base/+/4fe64fb4712a99d5da9c9a0eb8fd5169b252e1e1/omapi/java/android/se/omapi/Session.java#305
|
||||
// SecurityException is only thrown when Channel is constructed, which means everything else needs to succeed
|
||||
reader.slotIndex
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
if (omapiSlots.isEmpty()) {
|
||||
return CompatibilityResult(Compatibility.NOT_COMPATIBLE)
|
||||
}
|
||||
val formatChannelName = appContainer.customizableTextProvider::formatInternalChannelName
|
||||
return CompatibilityResult(
|
||||
Compatibility.COMPATIBLE,
|
||||
slotsOmapi = omapiSlots.map(formatChannelName),
|
||||
slotsIsdr = slots.map(formatChannelName),
|
||||
)
|
||||
}
|
||||
|
||||
open fun formatConclusion(result: CompatibilityResult): String {
|
||||
val usbHost = requireContext().packageManager
|
||||
.hasSystemFeature(PackageManager.FEATURE_USB_HOST)
|
||||
val resId = when (result.compatibility) {
|
||||
Compatibility.COMPATIBLE ->
|
||||
R.string.quick_compatibility_compatible
|
||||
|
||||
Compatibility.NOT_COMPATIBLE -> if (usbHost)
|
||||
R.string.quick_compatibility_not_compatible_but_usb else
|
||||
R.string.quick_compatibility_not_compatible
|
||||
}
|
||||
return getString(resId, getString(R.string.app_name))
|
||||
}
|
||||
|
||||
open fun formatDeviceInformation() = buildString {
|
||||
appendLine("BRAND: ${Build.BRAND}")
|
||||
appendLine("DEVICE: ${Build.DEVICE}")
|
||||
appendLine("MODEL: ${Build.MODEL}")
|
||||
appendLine("VERSION.RELEASE: ${Build.VERSION.RELEASE}")
|
||||
appendLine("VERSION.SDK_INT: ${Build.VERSION.SDK_INT}")
|
||||
}
|
||||
}
|
||||
|
||||
private inline val Reader.isSIM: Boolean
|
||||
get() = name.startsWith("SIM")
|
||||
|
||||
private inline val Reader.slotIndex: Int
|
||||
get() = (name.replace("SIM", "").toIntOrNull() ?: 1) - 1 // 0-based index
|
|
@ -1,11 +1,22 @@
|
|||
package im.angry.openeuicc.ui
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import im.angry.easyeuicc.R
|
||||
import im.angry.openeuicc.util.UnprivilegedEuiccContextMarker
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
class UnprivilegedMainActivity : MainActivity(), UnprivilegedEuiccContextMarker {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
if (runBlocking { !preferenceRepository.skipQuickCompatibilityFlow.first() }) {
|
||||
startActivity(Intent(this, QuickCompatibilityActivity::class.java))
|
||||
}
|
||||
}
|
||||
|
||||
class UnprivilegedMainActivity: MainActivity() {
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
super.onCreateOptionsMenu(menu)
|
||||
menuInflater.inflate(R.menu.activity_main_unprivileged, menu)
|
||||
|
@ -15,7 +26,7 @@ class UnprivilegedMainActivity: MainActivity() {
|
|||
override fun onOptionsItemSelected(item: MenuItem): Boolean =
|
||||
when (item.itemId) {
|
||||
R.id.compatibility_check -> {
|
||||
startActivity(Intent(this, CompatibilityCheckActivity::class.java))
|
||||
startActivity(Intent(this, QuickCompatibilityActivity::class.java))
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
|
|
|
@ -21,7 +21,7 @@ class UnprivilegedNoEuiccPlaceholderFragment : Fragment() {
|
|||
)
|
||||
|
||||
view.findViewById<View>(R.id.compatibility_check).setOnClickListener {
|
||||
startActivity(Intent(requireContext(), CompatibilityCheckActivity::class.java))
|
||||
startActivity(Intent(requireContext(), QuickCompatibilityActivity::class.java))
|
||||
}
|
||||
|
||||
return view
|
||||
|
|
|
@ -1,280 +0,0 @@
|
|||
package im.angry.openeuicc.util
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.se.omapi.Reader
|
||||
import android.telephony.TelephonyManager
|
||||
import im.angry.easyeuicc.R
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.IOException
|
||||
|
||||
fun getCompatibilityChecks(context: Context): List<CompatibilityCheck> =
|
||||
listOf(
|
||||
HasSystemFeaturesCheck(context),
|
||||
OmapiConnCheck(context),
|
||||
IsdrChannelAccessCheck(context),
|
||||
KnownBrokenCheck(context),
|
||||
UsbCheck(context),
|
||||
Verdict(context),
|
||||
)
|
||||
|
||||
inline fun <reified T: CompatibilityCheck> List<CompatibilityCheck>.findCheck(): T? =
|
||||
find { it.javaClass == T::class.java }?.let { it as T }
|
||||
|
||||
suspend fun List<CompatibilityCheck>.executeAll(callback: () -> Unit) = withContext(Dispatchers.IO) {
|
||||
forEach {
|
||||
it.run(this@executeAll)
|
||||
withContext(Dispatchers.Main) {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val Reader.isSIM: Boolean
|
||||
get() = name.startsWith("SIM")
|
||||
|
||||
private val Reader.slotIndex: Int
|
||||
get() = (name.replace("SIM", "").toIntOrNull() ?: 1)
|
||||
|
||||
abstract class CompatibilityCheck(context: Context) {
|
||||
enum class State {
|
||||
NOT_STARTED,
|
||||
IN_PROGRESS,
|
||||
SUCCESS,
|
||||
FAILURE_UNKNOWN, // The check technically failed, but no conclusion can be drawn
|
||||
FAILURE
|
||||
}
|
||||
|
||||
var state = State.NOT_STARTED
|
||||
|
||||
abstract val title: String
|
||||
protected abstract val defaultDescription: String
|
||||
protected lateinit var successDescription: String
|
||||
protected lateinit var failureDescription: String
|
||||
|
||||
val description: String
|
||||
get() = when {
|
||||
(state == State.FAILURE || state == State.FAILURE_UNKNOWN) && this::failureDescription.isInitialized -> failureDescription
|
||||
state == State.SUCCESS && this::successDescription.isInitialized -> successDescription
|
||||
else -> defaultDescription
|
||||
}
|
||||
|
||||
protected abstract suspend fun doCheck(allChecks: List<CompatibilityCheck>): State
|
||||
|
||||
suspend fun run(allChecks: List<CompatibilityCheck>) {
|
||||
state = State.IN_PROGRESS
|
||||
delay(200)
|
||||
state = try {
|
||||
doCheck(allChecks)
|
||||
} catch (_: Exception) {
|
||||
State.FAILURE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class HasSystemFeaturesCheck(private val context: Context): CompatibilityCheck(context) {
|
||||
override val title: String
|
||||
get() = context.getString(R.string.compatibility_check_system_features)
|
||||
override val defaultDescription: String
|
||||
get() = context.getString(R.string.compatibility_check_system_features_desc)
|
||||
|
||||
override suspend fun doCheck(allChecks: List<CompatibilityCheck>): State {
|
||||
if (!context.packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
|
||||
failureDescription = context.getString(R.string.compatibility_check_system_features_no_telephony)
|
||||
return State.FAILURE
|
||||
}
|
||||
|
||||
// We can check OMAPI UICC availability on R or later (if before R, we check OMAPI connectivity later)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !context.packageManager.hasSystemFeature(
|
||||
PackageManager.FEATURE_SE_OMAPI_UICC
|
||||
)) {
|
||||
failureDescription = context.getString(R.string.compatibility_check_system_features_no_omapi)
|
||||
return State.FAILURE_UNKNOWN
|
||||
}
|
||||
|
||||
return State.SUCCESS
|
||||
}
|
||||
}
|
||||
|
||||
internal class OmapiConnCheck(private val context: Context): CompatibilityCheck(context) {
|
||||
override val title: String
|
||||
get() = context.getString(R.string.compatibility_check_omapi_connectivity)
|
||||
override val defaultDescription: String
|
||||
get() = context.getString(R.string.compatibility_check_omapi_connectivity_desc)
|
||||
|
||||
override suspend fun doCheck(allChecks: List<CompatibilityCheck>): State {
|
||||
val seService = connectSEService(context)
|
||||
if (!seService.isConnected) {
|
||||
failureDescription = context.getString(R.string.compatibility_check_omapi_connectivity_fail)
|
||||
return State.FAILURE
|
||||
}
|
||||
|
||||
val tm = context.getSystemService(TelephonyManager::class.java)
|
||||
val simReaders = seService.readers.filter { it.isSIM }
|
||||
if (simReaders.isEmpty()) {
|
||||
failureDescription = context.getString(R.string.compatibility_check_omapi_connectivity_fail)
|
||||
return State.FAILURE_UNKNOWN
|
||||
} else if (simReaders.size < tm.activeModemCountCompat) {
|
||||
successDescription = context.getString(R.string.compatibility_check_omapi_connectivity_partial_success_sim_number,
|
||||
simReaders.map { it.slotIndex }.joinToString(", "))
|
||||
return State.SUCCESS
|
||||
}
|
||||
|
||||
return State.SUCCESS
|
||||
}
|
||||
}
|
||||
|
||||
internal class IsdrChannelAccessCheck(private val context: Context): CompatibilityCheck(context) {
|
||||
override val title: String
|
||||
get() = context.getString(R.string.compatibility_check_isdr_channel)
|
||||
override val defaultDescription: String
|
||||
get() = context.getString(R.string.compatibility_check_isdr_channel_desc)
|
||||
|
||||
override suspend fun doCheck(allChecks: List<CompatibilityCheck>): State {
|
||||
val seService = connectSEService(context)
|
||||
val readers = seService.readers.filter { it.isSIM }
|
||||
if (readers.isEmpty()) {
|
||||
failureDescription = context.getString(R.string.compatibility_check_isdr_channel_desc_unknown)
|
||||
return State.FAILURE_UNKNOWN
|
||||
}
|
||||
|
||||
val (validSlotIds, result) = readers.map {
|
||||
try {
|
||||
// Note: we ONLY check the default ISD-R AID, because this test is for the _device_,
|
||||
// NOT the eUICC. We don't care what AID a potential eUICC might use, all we need to
|
||||
// check is we can open _some_ AID.
|
||||
it.openSession().openLogicalChannel(EUICC_DEFAULT_ISDR_AID.decodeHex())?.close()
|
||||
Pair(it.slotIndex, State.SUCCESS)
|
||||
} catch (_: SecurityException) {
|
||||
// Ignore; this is expected when everything works
|
||||
// ref: https://android.googlesource.com/platform/frameworks/base/+/4fe64fb4712a99d5da9c9a0eb8fd5169b252e1e1/omapi/java/android/se/omapi/Session.java#305
|
||||
// SecurityException is only thrown when Channel is constructed, which means everything else needs to succeed
|
||||
Pair(it.slotIndex, State.SUCCESS)
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
if (e.message?.contains("Secure Element is not present") == true) {
|
||||
failureDescription = context.getString(R.string.compatibility_check_isdr_channel_desc_unknown)
|
||||
Pair(it.slotIndex, State.FAILURE_UNKNOWN)
|
||||
} else {
|
||||
Pair(it.slotIndex, State.FAILURE)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Pair(it.slotIndex, State.FAILURE)
|
||||
}
|
||||
}.fold(Pair(mutableListOf<Int>(), State.SUCCESS)) { (ids, result), (id, ok) ->
|
||||
if (ok != State.SUCCESS) {
|
||||
Pair(ids, ok)
|
||||
} else {
|
||||
Pair(ids.apply { add(id) }, result)
|
||||
}
|
||||
}
|
||||
|
||||
if (result != State.SUCCESS && validSlotIds.size > 0) {
|
||||
if (!context.packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_EUICC)) {
|
||||
failureDescription = context.getString(
|
||||
R.string.compatibility_check_isdr_channel_desc_partial_fail,
|
||||
validSlotIds.joinToString(", ")
|
||||
)
|
||||
} else {
|
||||
// If the device has embedded eSIMs, we can likely ignore the failure here;
|
||||
// the OMAPI failure likely resulted from trying to access internal eSIMs.
|
||||
return State.SUCCESS
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
internal class KnownBrokenCheck(private val context: Context): CompatibilityCheck(context) {
|
||||
companion object {
|
||||
val BROKEN_MANUFACTURERS = arrayOf("xiaomi", "huawei", "honor")
|
||||
}
|
||||
|
||||
override val title: String
|
||||
get() = context.getString(R.string.compatibility_check_known_broken)
|
||||
override val defaultDescription: String
|
||||
get() = context.getString(R.string.compatibility_check_known_broken_desc)
|
||||
|
||||
init {
|
||||
failureDescription = context.getString(R.string.compatibility_check_known_broken_fail)
|
||||
}
|
||||
|
||||
override suspend fun doCheck(allChecks: List<CompatibilityCheck>): State =
|
||||
if (Build.MANUFACTURER.lowercase() in BROKEN_MANUFACTURERS) {
|
||||
State.FAILURE
|
||||
} else {
|
||||
State.SUCCESS
|
||||
}
|
||||
}
|
||||
|
||||
internal class UsbCheck(private val context: Context) : CompatibilityCheck(context) {
|
||||
override val title: String
|
||||
get() = context.getString(R.string.compatibility_check_usb)
|
||||
override val defaultDescription: String
|
||||
get() = context.getString(R.string.compatibility_check_usb_desc)
|
||||
|
||||
init {
|
||||
successDescription = context.getString(R.string.compatibility_check_usb_ok)
|
||||
failureDescription = context.getString(R.string.compatibility_check_usb_fail)
|
||||
}
|
||||
|
||||
override suspend fun doCheck(allChecks: List<CompatibilityCheck>): State =
|
||||
if (context.packageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)) {
|
||||
State.SUCCESS
|
||||
} else {
|
||||
State.FAILURE
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal class Verdict(private val context: Context) : CompatibilityCheck(context) {
|
||||
override val title: String
|
||||
get() = context.getString(R.string.compatibility_check_verdict)
|
||||
override val defaultDescription: String
|
||||
get() = context.getString(R.string.compatibility_check_verdict_desc)
|
||||
|
||||
override suspend fun doCheck(allChecks: List<CompatibilityCheck>): State {
|
||||
if (allChecks.findCheck<KnownBrokenCheck>()?.state == State.FAILURE) {
|
||||
failureDescription = context.getString(
|
||||
R.string.compatibility_check_verdict_known_broken,
|
||||
context.getString(R.string.compatibility_check_verdict_fail_shared)
|
||||
)
|
||||
return State.FAILURE
|
||||
}
|
||||
|
||||
if (allChecks.findCheck<OmapiConnCheck>()?.state == State.SUCCESS &&
|
||||
allChecks.findCheck<IsdrChannelAccessCheck>()?.state == State.SUCCESS
|
||||
) {
|
||||
successDescription = context.getString(R.string.compatibility_check_verdict_ok)
|
||||
return State.SUCCESS
|
||||
}
|
||||
|
||||
if (allChecks.findCheck<OmapiConnCheck>()?.state == State.FAILURE_UNKNOWN ||
|
||||
allChecks.findCheck<IsdrChannelAccessCheck>()?.state == State.FAILURE_UNKNOWN
|
||||
) {
|
||||
// We are not sure because we can't fully check OMAPI
|
||||
// however we can guess based on feature flags
|
||||
// TODO: We probably need a "known-good" list for these devices as well?
|
||||
failureDescription = context.getString(
|
||||
if (allChecks.findCheck<HasSystemFeaturesCheck>()?.state == State.SUCCESS) {
|
||||
R.string.compatibility_check_verdict_unknown_likely_ok
|
||||
} else {
|
||||
R.string.compatibility_check_verdict_unknown_likely_fail
|
||||
},
|
||||
context.getString(R.string.compatibility_check_verdict_fail_shared)
|
||||
)
|
||||
return State.FAILURE_UNKNOWN
|
||||
}
|
||||
|
||||
failureDescription = context.getString(
|
||||
R.string.compatibility_check_verdict_unknown,
|
||||
context.getString(R.string.compatibility_check_verdict_fail_shared)
|
||||
)
|
||||
return State.FAILURE_UNKNOWN
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package im.angry.openeuicc.util
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
|
||||
internal object UnprivilegedPreferenceKeys {
|
||||
// ---- Miscellaneous ----
|
||||
val SKIP_QUICK_COMPATIBILITY = booleanPreferencesKey("skip_quick_compatibility")
|
||||
}
|
||||
|
||||
class UnprivilegedPreferenceRepository(context: Context) : PreferenceRepository(context) {
|
||||
// ---- Miscellaneous ----
|
||||
val skipQuickCompatibilityFlow = bindFlow(UnprivilegedPreferenceKeys.SKIP_QUICK_COMPATIBILITY, false)
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package im.angry.openeuicc.util
|
||||
|
||||
interface UnprivilegedEuiccContextMarker : OpenEuiccContextMarker {
|
||||
override val preferenceRepository: UnprivilegedPreferenceRepository
|
||||
get() = appContainer.preferenceRepository as UnprivilegedPreferenceRepository
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
<?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="match_parent">
|
||||
|
||||
<include layout="@layout/toolbar_activity" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/toolbar"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,16 @@
|
|||
<?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="match_parent">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/quick_compatibility_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,62 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:padding="32dp"
|
||||
android:textAlignment="center">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/quick_compatibility_conclusion"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textAlignment="center"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/quick_compatibility_device_information"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="monospace"
|
||||
android:lineHeight="30dp"
|
||||
android:textAlignment="center" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/quick_compatibility_result_slots"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/quick_compatibility_result_slots_isdr"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/quick_compatibility_result_notes"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/quick_compatibility_result_notes"
|
||||
android:visibility="gone" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/quick_compatibility_skip"
|
||||
android:visibility="gone"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/quick_compatibility_skip" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/quick_compatibility_button_continue"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="@string/quick_compatibility_button_continue" />
|
||||
|
||||
</LinearLayout>
|
|
@ -6,32 +6,15 @@
|
|||
<!-- Toast -->
|
||||
<string name="toast_ara_m_copied">ARA-M SHA-1 をクリップボードにコピーしました</string>
|
||||
<string name="toast_prompt_to_enable_sim_toolkit">「%s」アプリを有効化してください</string>
|
||||
<!-- Compatibility Check Descriptions -->
|
||||
<string name="compatibility_check_system_features">システムの機能</string>
|
||||
<string name="compatibility_check_system_features_desc">デバイスにリムーバブル eUICC カードの管理に必要なすべての機能が備わっているかどうか。例えば基本的な電話機能や OMAPI のサポートなど。</string>
|
||||
<string name="compatibility_check_system_features_no_telephony">使用しているデバイスには電話機能がありません。</string>
|
||||
<string name="compatibility_check_system_features_no_omapi">使用しているデバイスまたはシステムには OMAPI のサポートを宣言していません。これは、ハードウェアからのサポートが不足していることが原因の可能性があります。または、フラグが不足していることが原因の可能性もあります。OMAPI が実際にサポートされているかどうかを判断するには次の 2 つのチェック項目を参照してください。</string>
|
||||
<string name="compatibility_check_omapi_connectivity">OMAPI の接続</string>
|
||||
<string name="compatibility_check_omapi_connectivity_desc">使用しているデバイスは、OMAPI 経由で SIM カード上のセキュアエレメントへのアクセスを許可しているか否や。</string>
|
||||
<string name="compatibility_check_omapi_connectivity_fail">OMAPI 経由で SIM カードのセキュアエレメントリーダーを検出できません。このデバイスに SIM を挿入していない場合は、SIM を挿入後にこのチェックを再試行してください。</string>
|
||||
<string name="compatibility_check_omapi_connectivity_partial_success_sim_number">セキュアエレメントのアクセスが正常に検出されましたが、次の SIM スロットでのみ有効です: <b>SIM%s</b></string>
|
||||
<string name="compatibility_check_isdr_channel">ISD-R チャネルアクセス</string>
|
||||
<string name="compatibility_check_isdr_channel_desc">使用しているデバイスは、OMAPI 経由で eSIM への ISD-R (管理) チャネルを開くことをサポートしているか否や。</string>
|
||||
<string name="compatibility_check_isdr_channel_desc_unknown">OMAPI 経由での ISD-R アクセスがサポートされているかどうかを確認できません。まだ SIM カードが挿入されていない場合は、挿入した状態で再試行してください (どの SIM カードでも構いません)。</string>
|
||||
<string name="compatibility_check_isdr_channel_desc_partial_fail">ISD-R への OMAPI アクセスは、次のスロットでのみ可能です: <b>SIM%s</b></string>
|
||||
<string name="compatibility_check_known_broken">既知の破損リストの記載されていない</string>
|
||||
<string name="compatibility_check_known_broken_desc">取り外し可能な eSIM に関連するバグがデバイスに存在しないかを確認します。</string>
|
||||
<string name="compatibility_check_known_broken_fail">おっと...使用しているデバイスには、取り外し可能な eSIM へのアクセス時にバグが存在します。これは必ずしも全く機能しないことを意味するわけではありませんが、注意して進める必要があります。</string>
|
||||
<string name="compatibility_check_usb">USB カードリーダーのサポート</string>
|
||||
<string name="compatibility_check_usb_desc">使用しているデバイスは、USB カードリーダー経由の eSIM の管理をサポートしているか否や。</string>
|
||||
<string name="compatibility_check_usb_ok">このデバイスの標準 USB CCID リーダーを介して eSIM を管理できます (ここで他のチェック項目に失敗した場合でも)。カードリーダーを挿入し、このアプリを開いてこの方法で eSIM を管理できます。</string>
|
||||
<string name="compatibility_check_usb_fail">使用しているデバイスは USB ホストとしての機能をサポートしていません。</string>
|
||||
<string name="compatibility_check_verdict">判定 (USB 以外)</string>
|
||||
<string name="compatibility_check_verdict_desc">これまでのすべてのチェック項目に基づいて、デバイスに挿入された取り外し可能な eSIM の管理と互換性がある可能性はどの程度かについて</string>
|
||||
<string name="compatibility_check_verdict_ok">このデバイスに挿入された取り外し可能な eSIM の使用および管理が使用できる可能性があります。</string>
|
||||
<string name="compatibility_check_verdict_known_broken">挿入された取り外し可能な eSIM にアクセスするとデバイスにバグが発生することが知られています。\n%s</string>
|
||||
<string name="compatibility_check_verdict_unknown_likely_ok">挿入された取り外し可能な eSIM が使用しているデバイスで管理できるかはわかりません。ただし、このデバイスは OMAPI のサポートを宣言しているため、動作する可能性はわずかに高くなります。\n%s</string>
|
||||
<string name="compatibility_check_verdict_unknown_likely_fail">挿入された取り外し可能な eSIM がデバイス上で管理できるかどうかは判断できません。デバイスが OMAPI のサポートを宣言していないため、このデバイス上で取り外し可能な eSIM を管理することはサポートされていない可能性があります。\n%s</string>
|
||||
<string name="compatibility_check_verdict_unknown">挿入された取り外し可能な eSIM がデバイス上で管理できるかどうかを確認できません。\n%s</string>
|
||||
<string name="compatibility_check_verdict_fail_shared">ただし、eSIM プロファイルがすでに読み込まれている場合、有効化されたプロファイル自体は引き続き機能します。また、プロファイルが管理できない場合は、このデバイスで USB カードリーダーを介してプロファイルを管理できる可能性があります。</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>
|
||||
<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_skip">このメッセージを再度表示しない</string>
|
||||
<string name="quick_compatibility_unknown">不明</string>
|
||||
</resources>
|
||||
|
|
|
@ -1,32 +1,17 @@
|
|||
<resources>
|
||||
<string name="compatibility_check">兼容性检查</string>
|
||||
<string name="open_sim_toolkit">打开 SIM 卡应用程序</string>
|
||||
<string name="compatibility_check_system_features">系统功能</string>
|
||||
<string name="compatibility_check_system_features_desc">您的设备是否具有管理可插拔 eUICC 卡所需的所有功能。例如,基本的电话功能和 OMAPI 支持。</string>
|
||||
<string name="compatibility_check_system_features_no_telephony">您的设备没有电话功能。</string>
|
||||
<string name="compatibility_check_system_features_no_omapi">您的设备/系统未声明支持 OMAPI。这可能是由于缺少硬件支持,或者可能仅仅是由于缺少标志。请参阅以下两项检查以确定 OMAPI 是否确实受支持。</string>
|
||||
<string name="compatibility_check_omapi_connectivity">OMAPI 连接</string>
|
||||
<string name="compatibility_check_omapi_connectivity_desc">您的设备是否允许通过 OMAPI 访问 SIM 卡上的安全元件?</string>
|
||||
<string name="compatibility_check_omapi_connectivity_fail">无法通过 OMAPI 检测到 SIM 卡的 Secure Element。如果您尚未在此设备中插入 SIM 卡,请尝试插入一张 SIM 卡并重试此检查。</string>
|
||||
<string name="compatibility_check_omapi_connectivity_partial_success_sim_number">已成功检测到可访问 Secure Element 的卡槽,但仅限于以下 SIM 卡槽:<b>SIM%s</b>。</string>
|
||||
<string name="compatibility_check_isdr_channel">ISD-R 通道访问</string>
|
||||
<string name="compatibility_check_isdr_channel_desc">您的设备是否支持通过 OMAPI 打开 eSIM 的 ISD-R (管理) 通道?</string>
|
||||
<string name="compatibility_check_isdr_channel_desc_unknown">无法确定是否支持通过 OMAPI 进行 ISD-R 访问。如果尚未插入,您可能需要插入 SIM 卡 (任何 SIM 卡都可以) 重试。</string>
|
||||
<string name="compatibility_check_isdr_channel_desc_partial_fail">OMAPI 只能在以下 SIM 插槽上访问 ISD-R:<b>SIM%s</b>。</string>
|
||||
<string name="compatibility_check_known_broken">不在已知的 BUG 名单中</string>
|
||||
<string name="compatibility_check_known_broken_desc">确保您的设备不存在与可插拔 eSIM 相关的错误。</string>
|
||||
<string name="compatibility_check_known_broken_fail">糟糕,您的设备在访问可插拔 eSIM 时存在错误。这并不表示完全无法使用,但我们不保证该应用在您设备上的行为。</string>
|
||||
<string name="compatibility_check_usb">USB 读卡器支持</string>
|
||||
<string name="compatibility_check_usb_desc">您的设备是否支持通过 USB 读卡器管理 eSIM?</string>
|
||||
<string name="compatibility_check_usb_ok">您可以通过此设备上的标准 USB CCID 读取器管理 eSIM (即使您在这里有任何其他检查项失败)。请插入读卡器,然后打开此应用程序以这种方式管理 eSIM。</string>
|
||||
<string name="compatibility_check_usb_fail">您的设备不支持 USB 读卡器。</string>
|
||||
<string name="compatibility_check_verdict">结论 (USB 读卡器以外)</string>
|
||||
<string name="compatibility_check_verdict_desc">根据之前的所有检查,您的设备与可插拔 eSIM 卡兼容的可能性有多大?</string>
|
||||
<string name="compatibility_check_verdict_ok">您可以使用和管理插入此设备的可插拔 eSIM 卡。</string>
|
||||
<string name="compatibility_check_verdict_known_broken">已知您的设备在访问可插拔 eSIM 卡时存在问题。\n%s</string>
|
||||
<string name="compatibility_check_verdict_unknown_likely_ok">我们无法确定是否可以在您的设备上管理可插拔 eSIM 卡。不过,您的设备确实声明支持 OMAPI,因此它工作的可能性略高。\n%s</string>
|
||||
<string name="compatibility_check_verdict_unknown_likely_fail">我们无法确定是否可以在您的设备上管理可插拔 eSIM 卡。由于您的设备未声明支持OMAPI,因此更有可能不支持在此设备上管理可插拔 eSIM。\n%s</string>
|
||||
<string name="compatibility_check_verdict_unknown">我们无法确定是否可以在您的设备上管理可插拔 eSIM 卡。\n%s</string>
|
||||
<string name="compatibility_check_verdict_fail_shared">然而,已经加载了eSIM配置文件的可插拔 eSIM 卡仍然可以工作; 即使无法在装置上直接管理可插拔 eSIM 卡中的配置文件,您仍然可以使用 USB 卡读卡器来管理配置文件。</string>
|
||||
<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 的卡片</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">注意:以上结果仅供参考。即使某些卡槽没有被列举出来,插卡后也可能可用。</string>
|
||||
<string name="quick_compatibility_result_notes_incompatible">注意:如果您目前没有插卡,请插任意 SIM 卡后重试兼容性检测。</string>
|
||||
<string name="quick_compatibility_button_continue">继续</string>
|
||||
<string name="quick_compatibility_skip">不再显示此消息</string>
|
||||
<string name="quick_compatibility_unknown">未知</string>
|
||||
</resources>
|
|
@ -1,32 +1,17 @@
|
|||
<resources>
|
||||
<string name="compatibility_check">相容性檢查</string>
|
||||
<string name="open_sim_toolkit">啟動 SIM 卡應用程式</string>
|
||||
<string name="compatibility_check_system_features">系統功能</string>
|
||||
<string name="compatibility_check_system_features_desc">您的裝置是否具有管理可插拔 eUICC 卡所需的所有功能。例如,基本的電話功能和 OMAPI 支援。</string>
|
||||
<string name="compatibility_check_system_features_no_telephony">您的裝置沒有電話功能。</string>
|
||||
<string name="compatibility_check_system_features_no_omapi">您的裝置/系統未宣告支援 OMAPI。這可能是由於缺少硬體支援,或者可能僅僅是由於缺少標誌。請參閱以下兩項檢查以確定 OMAPI 是否確實受支援。</string>
|
||||
<string name="compatibility_check_omapi_connectivity">OMAPI 連線</string>
|
||||
<string name="compatibility_check_omapi_connectivity_desc">您的裝置是否允許透過 OMAPI 存取 SIM 卡上的安全元件?</string>
|
||||
<string name="compatibility_check_omapi_connectivity_fail">無法透過 OMAPI 偵測到 SIM 卡的 Secure Element。如果您尚未在此裝置中插入 SIM 卡,請嘗試插入一張 SIM 卡並重試此檢查。</string>
|
||||
<string name="compatibility_check_omapi_connectivity_partial_success_sim_number">已成功檢測到可存取 Secure Element 的卡槽,但僅限於以下 SIM 卡槽:<b>SIM%s</b>。</string>
|
||||
<string name="compatibility_check_isdr_channel">ISD-R 通道存取</string>
|
||||
<string name="compatibility_check_isdr_channel_desc">您的裝置是否支援透過 OMAPI 開啟 eSIM 的 ISD-R (管理) 通道?</string>
|
||||
<string name="compatibility_check_isdr_channel_desc_unknown">無法確定是否支援透過 OMAPI 進行 ISD-R 的存取。如果尚未插入,您可能需要插入 SIM 卡 (任何 SIM 卡都可以) 重試。</string>
|
||||
<string name="compatibility_check_isdr_channel_desc_partial_fail">OMAPI 只能在以下 SIM 插槽上存取 ISD-R:<b>SIM%s</b>。</string>
|
||||
<string name="compatibility_check_known_broken">不在已知錯誤清單中</string>
|
||||
<string name="compatibility_check_known_broken_desc">確保您的裝置不存在與可插拔 eSIM 相關的錯誤。</string>
|
||||
<string name="compatibility_check_known_broken_fail">很抱歉,您的裝置在存取可插拔 eSIM 時存在錯誤。這並不表示完全無法使用,但我們不保證該應用在您裝置上的行為。</string>
|
||||
<string name="compatibility_check_usb">USB 晶片讀卡機支援</string>
|
||||
<string name="compatibility_check_usb_desc">您的裝置是否支援透過 USB 晶片讀卡機管理 eSIM?</string>
|
||||
<string name="compatibility_check_usb_ok">您可以透過此裝置上的標準 USB CCID 讀卡機管理 eSIM (即使您在這裡有任何其他檢查項失敗)。請插入讀卡機,然後開啟此應用程式以這種方式管理 eSIM。</string>
|
||||
<string name="compatibility_check_usb_fail">您的裝置不支援 USB 晶片讀卡機。</string>
|
||||
<string name="compatibility_check_verdict">結論 (USB 晶片讀卡機以外)</string>
|
||||
<string name="compatibility_check_verdict_desc">根據之前的所有檢查,您的裝置與可插拔 eSIM 卡相容的可能性有多大?</string>
|
||||
<string name="compatibility_check_verdict_ok">您可以使用和管理插入此裝置的可插拔 eSIM 卡。</string>
|
||||
<string name="compatibility_check_verdict_known_broken">已知您的裝置在存取可插拔 eSIM 卡時存在問題。\n%s</string>
|
||||
<string name="compatibility_check_verdict_unknown_likely_ok">我們無法確定是否可以在您的裝置上管理可插拔 eSIM 卡。不過,您的裝置確實宣告支援 OMAPI,因此它工作的可能性略高。\n%s</string>
|
||||
<string name="compatibility_check_verdict_unknown_likely_fail">我們無法確定是否可以在您的裝置上管理可插拔 eSIM 卡。由於您的裝置未宣告支援OMAPI,因此更有可能不支援在此裝置上管理可插拔 eSIM。\n%s</string>
|
||||
<string name="compatibility_check_verdict_unknown">我們無法確定是否可以在您的裝置上管理可插拔 eSIM 卡。\n%s</string>
|
||||
<string name="compatibility_check_verdict_fail_shared">然而,已經載入了eSIM設定檔的可插拔 eSIM 卡仍然可以工作; 即使無法在裝置上直接管理可插拔 eSIM 卡中的設定檔,您仍然可以使用 USB 卡讀卡機來管理設定檔。</string>
|
||||
<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 的卡片</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">注意:以上結果僅供參考。即使某些卡槽沒有被列舉出來,插卡後也可能可用。</string>
|
||||
<string name="quick_compatibility_result_notes_incompatible">注意:如果您目前沒有插卡,請插任何 SIM 卡後重試相容性檢測。</string>
|
||||
<string name="quick_compatibility_button_continue">繼續</string>
|
||||
<string name="quick_compatibility_skip">不再顯示此訊息</string>
|
||||
<string name="quick_compatibility_unknown">未知</string>
|
||||
</resources>
|
|
@ -11,32 +11,16 @@
|
|||
<string name="toast_ara_m_copied">ARA-M SHA-1 copied to clipboard</string>
|
||||
<string name="toast_prompt_to_enable_sim_toolkit">Please ENABLE your \"%s\" application</string>
|
||||
|
||||
<!-- Compatibility Check Descriptions -->
|
||||
<string name="compatibility_check_system_features">System Features</string>
|
||||
<string name="compatibility_check_system_features_desc">Whether your device has all the required features for managing removable eUICC cards. For example, basic telephony and OMAPI support.</string>
|
||||
<string name="compatibility_check_system_features_no_telephony">Your device has no telephony features.</string>
|
||||
<string name="compatibility_check_system_features_no_omapi">Your device / system does not declare support for OMAPI. This could be due to missing support from hardware, or it could be simply due to a missing flag. See the following two checks to determine whether OMAPI is actually supported or not.</string>
|
||||
<string name="compatibility_check_omapi_connectivity">OMAPI Connectivity</string>
|
||||
<string name="compatibility_check_omapi_connectivity_desc">Does your device allow access to Secure Elements on SIM cards via OMAPI?</string>
|
||||
<string name="compatibility_check_omapi_connectivity_fail">Unable to detect Secure Element readers for SIM cards via OMAPI. If you have not inserted a SIM in this device, try inserting one and retry this check.</string>
|
||||
<string name="compatibility_check_omapi_connectivity_partial_success_sim_number">Successfully detected Secure Element access, but only for the following SIM slots: <b>SIM%s</b>.</string>
|
||||
<string name="compatibility_check_isdr_channel">ISD-R Channel Access</string>
|
||||
<string name="compatibility_check_isdr_channel_desc">Does your device support opening an ISD-R (management) channel to eSIMs via OMAPI?</string>
|
||||
<string name="compatibility_check_isdr_channel_desc_unknown">Cannot determine whether ISD-R access through OMAPI is supported. You might want to retry with SIM cards inserted (any SIM card will do) if not already.</string>
|
||||
<string name="compatibility_check_isdr_channel_desc_partial_fail">OMAPI access to ISD-R is only possible on the following SIM slots: <b>SIM%s</b>.</string>
|
||||
<string name="compatibility_check_known_broken">Not on the Known Broken List</string>
|
||||
<string name="compatibility_check_known_broken_desc">Making sure your device is not known to have bugs associated with removable eSIMs.</string>
|
||||
<string name="compatibility_check_known_broken_fail">Oops, your device is known to have bugs when accessing removable eSIMs. This does not necessarily mean that it will not work at all, but you will have to proceed with caution.</string>
|
||||
<string name="compatibility_check_usb">USB Card Reader Support</string>
|
||||
<string name="compatibility_check_usb_desc">Does your device support managing eSIMs via USB card readers?</string>
|
||||
<string name="compatibility_check_usb_ok">You can manage eSIMs through standard USB CCID readers on this device (even if you had any other check items fail here). Insert the card reader and then open this app to manage eSIMs in this way.</string>
|
||||
<string name="compatibility_check_usb_fail">Your device does not support acting as a USB host.</string>
|
||||
<string name="compatibility_check_verdict">Verdict (non-USB)</string>
|
||||
<string name="compatibility_check_verdict_desc">Based on all previous checks, how likely is your device to be compatible with managing inserted removable eSIMs?</string>
|
||||
<string name="compatibility_check_verdict_ok">You can likely use and manage removable eSIMs inserted into this device.</string>
|
||||
<string name="compatibility_check_verdict_known_broken">Your device is known to be buggy when accessing inserted removable eSIMs.\n%s</string>
|
||||
<string name="compatibility_check_verdict_unknown_likely_ok">We cannot determine whether inserted removable eSIMs can be managed on your device. Your device does declare support for OMAPI, though, so it is slightly more likely that it will work.\n%s</string>
|
||||
<string name="compatibility_check_verdict_unknown_likely_fail">We cannot determine whether inserted removable eSIMs can be managed on your device. Since your device does not declare support for OMAPI, it is more likely that managing removable eSIMs on this device is unsupported.\n%s</string>
|
||||
<string name="compatibility_check_verdict_unknown">We cannot determine whether inserted removable eSIMs can be managed on your device.\n%s</string>
|
||||
<string name="compatibility_check_verdict_fail_shared">However, a removable eSIM that has already been loaded with an eSIM profile will still work; you can also most likely use a USB card reader plugged into this device to manage profiles, even if you cannot manage one inserted into your device.</string>
|
||||
<!-- Quick Compatibility -->
|
||||
<string name="quick_compatibility">Quick Compatibility Check</string>
|
||||
<string name="quick_compatibility_compatible">Your smartphone can manage %s-compatible cards</string>
|
||||
<string name="quick_compatibility_not_compatible">Your smartphone is not compatible with %s</string>
|
||||
<string name="quick_compatibility_not_compatible_but_usb">Your smartphone is not fully compatible with %s. However, you can still use a USB smart card reader for near-full functionality.</string>
|
||||
<string name="quick_compatibility_result_slots">Accessible slots: %s</string>
|
||||
<string name="quick_compatibility_result_slots_isdr">ISD-R access: %s</string>
|
||||
<string name="quick_compatibility_result_notes">Note: these results are for reference only. Even if a SIM slot is not listed above, it <i>may</i> be compatible as well once a SIM card is inserted.</string>
|
||||
<string name="quick_compatibility_result_notes_incompatible">Note: if you currently do not have any SIM card inserted, try the compatibility check again after inserting one. Any SIM card will do.</string>
|
||||
<string name="quick_compatibility_button_continue">Continue</string>
|
||||
<string name="quick_compatibility_skip">Don\'t show this message again</string>
|
||||
<string name="quick_compatibility_unknown">Unknown</string>
|
||||
</resources>
|
|
@ -2,7 +2,6 @@ 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
|
||||
|
@ -32,7 +31,7 @@ class PrivilegedEuiccChannelFactory(context: Context) : DefaultEuiccChannelFacto
|
|||
)
|
||||
try {
|
||||
return EuiccChannelImpl(
|
||||
context.getString(R.string.telephony_manager),
|
||||
context.getString(R.string.channel_type_telephony_manager),
|
||||
port,
|
||||
intrinsicChannelName = null,
|
||||
TelephonyManagerApduInterface(
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="no_euicc_priv">このデバイスで eUICC が見つかりません。\nデバイスによってはアプリのメニューからデュアル SIM を有効化する必要があります。</string>
|
||||
<string name="telephony_manager">TelephonyManager (特権)</string>
|
||||
<string name="channel_type_telephony_manager">TelephonyManager (特権)</string>
|
||||
<string name="dsds">デュアル SIM</string>
|
||||
<string name="toast_dsds_switched">DSDS の状態が切り替わりました。モデムが再起動するまでお待ちください。</string>
|
||||
<string name="footer_mep">このスロットは MEP (Multiple Enabled Profiles) をサポートしています。この機能を有効化または無効化するには「スロットマッピングツール」を使用してください。</string>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<string name="lui_desc">您的设备支持 eSIM。要连接到移动网络,请下载运营商发布的 eSIM,或插入物理 SIM 卡。</string>
|
||||
<string name="lui_skip">跳过</string>
|
||||
<string name="lui_download">下载 eSIM</string>
|
||||
<string name="telephony_manager">TelephonyManager (特权)</string>
|
||||
<string name="channel_type_telephony_manager">TelephonyManager (特权)</string>
|
||||
<string name="pref_developer_telephony_manager_removable">全局使用 TelephonyManager</string>
|
||||
<string name="pref_developer_telephony_manager_removable_desc">在默认情况下,可移除 eUICC 将仅使用 OMAPI。这与非特权模式 (EasyEUICC) 一致。在某些设备上 OMAPI 可能存在问题 -- 选择此选项以强制使用 TelephonyManager。</string>
|
||||
</resources>
|
|
@ -16,7 +16,7 @@
|
|||
<string name="lui_desc">您的裝置支援 eSIM。要連線到行動網路,請下載電信業者釋出的 eSIM,或插入實體 SIM 卡。</string>
|
||||
<string name="lui_skip">跳過</string>
|
||||
<string name="lui_download">下載 eSIM</string>
|
||||
<string name="telephony_manager">TelephonyManager (特權)</string>
|
||||
<string name="channel_type_telephony_manager">TelephonyManager (特權)</string>
|
||||
<string name="pref_developer_telephony_manager_removable">全域使用 TelephonyManager</string>
|
||||
<string name="pref_developer_telephony_manager_removable_desc">在預設情況下,可移除 eUICC 將僅使用 OMAPI。這與非特權模式 (EasyEUICC) 一致。在某些裝置上 OMAPI 可能有問題 -- 選擇此選項以強制使用 TelephonyManager。</string>
|
||||
</resources>
|
|
@ -1,7 +1,7 @@
|
|||
<resources>
|
||||
<string name="app_name" translatable="false">OpenEUICC</string>
|
||||
<string name="no_euicc_priv">No eUICC found on this device.\nOn some devices, you may need to enable dual SIM first in the menu of this app.</string>
|
||||
<string name="telephony_manager">TelephonyManager (Privileged)</string>
|
||||
<string name="channel_type_telephony_manager">TelephonyManager (Privileged)</string>
|
||||
|
||||
<string name="dsds">Dual SIM</string>
|
||||
|
||||
|
|
|
@ -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, *rx);
|
||||
(*env)->GetByteArrayRegion(env, ret, 0, *rx_len, (jbyte *) *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, *rx);
|
||||
(*env)->GetByteArrayRegion(env, rxArr, 0, *rx_len, (jbyte *) *rx);
|
||||
(*env)->DeleteLocalRef(env, txArr);
|
||||
(*env)->DeleteLocalRef(env, rxArr);
|
||||
(*env)->DeleteLocalRef(env, headersArr);
|
||||
|
|
Loading…
Add table
Reference in a new issue