Compare commits

...

2 commits

Author SHA1 Message Date
db9fd3bcf2
feat: discovery 2025-03-10 09:29:34 +08:00
88eb1ce0e2 feat: update TelephonyManager preference key and implement context marker interface (#167)
Reviewed-on: PeterCxy/OpenEUICC#167
Co-authored-by: septs <github@septs.pw>
Co-committed-by: septs <github@septs.pw>
2025-03-09 22:47:02 +01:00
28 changed files with 430 additions and 165 deletions

View file

@ -1,9 +1,11 @@
package im.angry.openeuicc.core package im.angry.openeuicc.core
import net.typeblog.lpac_jni.EuiccConfiguredAddresses
import net.typeblog.lpac_jni.EuiccInfo2 import net.typeblog.lpac_jni.EuiccInfo2
import net.typeblog.lpac_jni.LocalProfileAssistant import net.typeblog.lpac_jni.LocalProfileAssistant
import net.typeblog.lpac_jni.LocalProfileInfo import net.typeblog.lpac_jni.LocalProfileInfo
import net.typeblog.lpac_jni.LocalProfileNotification import net.typeblog.lpac_jni.LocalProfileNotification
import net.typeblog.lpac_jni.ProfileDiscoveryCallback
import net.typeblog.lpac_jni.ProfileDownloadCallback import net.typeblog.lpac_jni.ProfileDownloadCallback
class LocalProfileAssistantWrapper(orig: LocalProfileAssistant) : class LocalProfileAssistantWrapper(orig: LocalProfileAssistant) :
@ -32,6 +34,9 @@ class LocalProfileAssistantWrapper(orig: LocalProfileAssistant) :
override fun setEs10xMss(mss: Byte) = lpa.setEs10xMss(mss) override fun setEs10xMss(mss: Byte) = lpa.setEs10xMss(mss)
override fun getEuiccConfiguredAddresses(): EuiccConfiguredAddresses =
lpa.getEuiccConfiguredAddresses()
override fun enableProfile(iccid: String, refresh: Boolean): Boolean = override fun enableProfile(iccid: String, refresh: Boolean): Boolean =
lpa.enableProfile(iccid, refresh) lpa.enableProfile(iccid, refresh)
@ -48,6 +53,9 @@ class LocalProfileAssistantWrapper(orig: LocalProfileAssistant) :
callback: ProfileDownloadCallback callback: ProfileDownloadCallback
) = lpa.downloadProfile(smdp, matchingId, imei, confirmationCode, callback) ) = lpa.downloadProfile(smdp, matchingId, imei, confirmationCode, callback)
override fun discoveryProfile(smds: String, imei: String?, callback: ProfileDiscoveryCallback) =
lpa.discoveryProfile(smds, imei, callback)
override fun deleteNotification(seqNumber: Long): Boolean = lpa.deleteNotification(seqNumber) override fun deleteNotification(seqNumber: Long): Boolean = lpa.deleteNotification(seqNumber)
override fun handleNotification(seqNumber: Long): Boolean = lpa.handleNotification(seqNumber) override fun handleNotification(seqNumber: Long): Boolean = lpa.handleNotification(seqNumber)

View file

@ -72,6 +72,9 @@ open class SettingsFragment: PreferenceFragmentCompat() {
requirePreference<CheckBoxPreference>("pref_advanced_verbose_logging") requirePreference<CheckBoxPreference>("pref_advanced_verbose_logging")
.bindBooleanFlow(preferenceRepository.verboseLoggingFlow) .bindBooleanFlow(preferenceRepository.verboseLoggingFlow)
requirePreference<CheckBoxPreference>("pref_developer_discovery_profile")
.bindBooleanFlow(preferenceRepository.discoveryProfileFlow)
requirePreference<CheckBoxPreference>("pref_developer_unfiltered_profile_list") requirePreference<CheckBoxPreference>("pref_developer_unfiltered_profile_list")
.bindBooleanFlow(preferenceRepository.unfilteredProfileListFlow) .bindBooleanFlow(preferenceRepository.unfilteredProfileListFlow)

View file

@ -21,6 +21,8 @@ import im.angry.openeuicc.ui.BaseEuiccAccessActivity
import im.angry.openeuicc.util.* import im.angry.openeuicc.util.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import net.typeblog.lpac_jni.EuiccConfiguredAddresses
import net.typeblog.lpac_jni.LocalProfileAssistant import net.typeblog.lpac_jni.LocalProfileAssistant
class DownloadWizardActivity: BaseEuiccAccessActivity() { class DownloadWizardActivity: BaseEuiccAccessActivity() {
@ -36,6 +38,7 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() {
var downloadError: LocalProfileAssistant.ProfileDownloadException?, var downloadError: LocalProfileAssistant.ProfileDownloadException?,
var skipMethodSelect: Boolean, var skipMethodSelect: Boolean,
var confirmationCodeRequired: Boolean, var confirmationCodeRequired: Boolean,
var configuredAddresses: EuiccConfiguredAddresses?
) )
private lateinit var state: DownloadWizardState private lateinit var state: DownloadWizardState
@ -75,6 +78,7 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() {
downloadError = null, downloadError = null,
skipMethodSelect = false, skipMethodSelect = false,
confirmationCodeRequired = false, confirmationCodeRequired = false,
configuredAddresses = null,
) )
handleDeepLink() handleDeepLink()

View file

@ -11,6 +11,8 @@ import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
@ -21,22 +23,21 @@ import com.journeyapps.barcodescanner.ScanOptions
import im.angry.openeuicc.common.R import im.angry.openeuicc.common.R
import im.angry.openeuicc.util.* import im.angry.openeuicc.util.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
class DownloadWizardMethodSelectFragment : DownloadWizardActivity.DownloadWizardStepFragment() { class DownloadWizardMethodSelectFragment : DownloadWizardActivity.DownloadWizardStepFragment() {
data class DownloadMethod( companion object {
val iconRes: Int, const val TAG = "DownloadWizardMethodSelectFragment"
val titleRes: Int, }
val onClick: () -> Unit
)
// TODO: Maybe we should find a better barcode scanner (or an external one?) // TODO: Maybe we should find a better barcode scanner (or an external one?)
private val barcodeScannerLauncher = registerForActivityResult(ScanContract()) { result -> private val barcodeScannerLauncher =
result.contents?.let { content -> registerForActivityResult(ScanContract()) {
processLpaString(content) it.contents?.let(::processLPAString)
} }
}
private val gallerySelectorLauncher = private val gallerySelectorLauncher =
registerForActivityResult(ActivityResultContracts.GetContent()) { result -> registerForActivityResult(ActivityResultContracts.GetContent()) { result ->
@ -51,16 +52,16 @@ class DownloadWizardMethodSelectFragment : DownloadWizardActivity.DownloadWizard
} }
} }
decoded.getOrNull()?.let { processLpaString(it) } decoded.getOrNull()?.let { processLPAString(it) }
} }
} }
val downloadMethods = arrayOf( private fun getDownloadMethods() = arrayOf(
DownloadMethod(R.drawable.ic_scan_black, R.string.download_wizard_method_qr_code) { DownloadMethod(R.drawable.ic_scan_black, R.string.download_wizard_method_qr_code) {
barcodeScannerLauncher.launch(ScanOptions().apply { val options = ScanOptions()
setDesiredBarcodeFormats(ScanOptions.QR_CODE) .setDesiredBarcodeFormats(ScanOptions.QR_CODE)
setOrientationLocked(false) .setOrientationLocked(false)
}) barcodeScannerLauncher.launch(options)
}, },
DownloadMethod(R.drawable.ic_gallery_black, R.string.download_wizard_method_gallery) { DownloadMethod(R.drawable.ic_gallery_black, R.string.download_wizard_method_gallery) {
gallerySelectorLauncher.launch("image/*") gallerySelectorLauncher.launch("image/*")
@ -68,13 +69,17 @@ class DownloadWizardMethodSelectFragment : DownloadWizardActivity.DownloadWizard
DownloadMethod(R.drawable.ic_paste_go, R.string.download_wizard_method_clipboard) { DownloadMethod(R.drawable.ic_paste_go, R.string.download_wizard_method_clipboard) {
handleLoadFromClipboard() handleLoadFromClipboard()
}, },
DownloadMethod(R.drawable.ic_search, R.string.download_wizard_method_discovery, isDiscoverable()) {
gotoNextFragment(DownloadWizardDetailsFragment())
},
DownloadMethod(R.drawable.ic_edit, R.string.download_wizard_method_manual) { DownloadMethod(R.drawable.ic_edit, R.string.download_wizard_method_manual) {
gotoNextFragment(DownloadWizardDetailsFragment()) gotoNextFragment(DownloadWizardDetailsFragment())
} },
) )
override val hasNext: Boolean override val hasNext: Boolean
get() = false get() = false
override val hasPrev: Boolean override val hasPrev: Boolean
get() = true get() = true
@ -88,38 +93,29 @@ class DownloadWizardMethodSelectFragment : DownloadWizardActivity.DownloadWizard
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View =
val view = inflater.inflate(R.layout.fragment_download_method_select, container, false) inflater.inflate(R.layout.fragment_download_method_select, container, false).apply {
val recyclerView = view.requireViewById<RecyclerView>(R.id.download_method_list) requireViewById<RecyclerView>(R.id.download_method_list).apply {
recyclerView.adapter = DownloadMethodAdapter() adapter = DownloadMethodAdapter(getDownloadMethods())
recyclerView.layoutManager = layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
LinearLayoutManager(view.context, LinearLayoutManager.VERTICAL, false) addItemDecoration(DividerItemDecoration(context, LinearLayoutManager.VERTICAL))
recyclerView.addItemDecoration( }
DividerItemDecoration( }
requireContext(),
LinearLayoutManager.VERTICAL
)
)
return view
}
private fun handleLoadFromClipboard() { private fun handleLoadFromClipboard() {
val clipboard = requireContext().getSystemService(ClipboardManager::class.java) val clipboard = requireContext().getSystemService(ClipboardManager::class.java)
val text = clipboard.primaryClip?.getItemAt(0)?.text val text = clipboard.primaryClip?.getItemAt(0)?.text
if (text == null) { if (text == null) {
Toast.makeText( val resId = R.string.profile_download_no_lpa_string
requireContext(), Toast.makeText(requireContext(), resId, Toast.LENGTH_SHORT).show()
R.string.profile_download_no_lpa_string,
Toast.LENGTH_SHORT
).show()
return return
} }
processLpaString(text.toString()) processLPAString(text.toString())
} }
private fun processLpaString(input: String) { private fun processLPAString(input: String) {
try { try {
val parsed = LPAString.parse(input) val parsed = LPAString.parse(input)
state.smdp = parsed.address state.smdp = parsed.address
@ -137,37 +133,46 @@ class DownloadWizardMethodSelectFragment : DownloadWizardActivity.DownloadWizard
} }
} }
private inner class DownloadMethodViewHolder(private val root: View) : ViewHolder(root) { private fun isDiscoverable(): Boolean {
private val icon = root.requireViewById<ImageView>(R.id.download_method_icon) val discoverable = runBlocking { preferenceRepository.discoveryProfileFlow.first() }
private val title = root.requireViewById<TextView>(R.id.download_method_title) if (!discoverable) return false
return state.configuredAddresses?.discoverable ?: false
fun bind(item: DownloadMethod) {
icon.setImageResource(item.iconRes)
title.setText(item.titleRes)
root.setOnClickListener {
// If the user elected to use another download method, reset the confirmation code flag
// too
state.confirmationCodeRequired = false
item.onClick()
}
}
} }
}
private inner class DownloadMethodAdapter : RecyclerView.Adapter<DownloadMethodViewHolder>() { private data class DownloadMethod(
override fun onCreateViewHolder( @DrawableRes
parent: ViewGroup, val iconRes: Int,
viewType: Int @StringRes
): DownloadMethodViewHolder { val titleRes: Int,
val view = LayoutInflater.from(parent.context) val isEnabled: Boolean = true,
.inflate(R.layout.download_method_item, parent, false) val onClick: () -> Unit,
return DownloadMethodViewHolder(view) )
}
override fun getItemCount(): Int = downloadMethods.size private class DownloadMethodViewHolder(private val root: View) : ViewHolder(root) {
private val icon: ImageView = root.requireViewById(R.id.download_method_icon)
private val title: TextView = root.requireViewById(R.id.download_method_title)
override fun onBindViewHolder(holder: DownloadMethodViewHolder, position: Int) { fun bind(item: DownloadMethod) {
holder.bind(downloadMethods[position]) icon.setImageResource(item.iconRes)
} title.setText(item.titleRes)
root.setOnClickListener { item.onClick() }
}
}
private class DownloadMethodAdapter(methods: Array<DownloadMethod>) :
RecyclerView.Adapter<DownloadMethodViewHolder>() {
private val methods = methods.filter(DownloadMethod::isEnabled)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
LayoutInflater.from(parent.context)
.inflate(R.layout.download_method_item, parent, false)
.let { DownloadMethodViewHolder(it) }
override fun getItemCount(): Int = methods.size
override fun onBindViewHolder(holder: DownloadMethodViewHolder, position: Int) {
holder.bind(methods[position])
} }
} }

View file

@ -19,7 +19,7 @@ import im.angry.openeuicc.util.*
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.toList import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.typeblog.lpac_jni.LocalProfileInfo import net.typeblog.lpac_jni.EuiccConfiguredAddresses
class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardStepFragment() { class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardStepFragment() {
companion object { companion object {
@ -37,6 +37,7 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt
val imei: String, val imei: String,
val enabledProfileName: String?, val enabledProfileName: String?,
val intrinsicChannelName: String?, val intrinsicChannelName: String?,
val configuredAddresses: EuiccConfiguredAddresses?,
) )
private var loaded = false private var loaded = false
@ -117,6 +118,7 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt
}, },
channel.lpa.profiles.enabled?.displayName, channel.lpa.profiles.enabled?.displayName,
channel.intrinsicChannelName, channel.intrinsicChannelName,
channel.lpa.getEuiccConfiguredAddresses(),
) )
} }
}.toList().sortedBy { it.logicalSlotId } }.toList().sortedBy { it.logicalSlotId }
@ -129,6 +131,7 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt
} else { } else {
if (slots.isNotEmpty()) { if (slots.isNotEmpty()) {
state.selectedLogicalSlot = slots[0].logicalSlotId state.selectedLogicalSlot = slots[0].logicalSlotId
state.configuredAddresses = slots[0].configuredAddresses
} }
0 0
} }
@ -168,8 +171,11 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt
adapter.notifyItemChanged(lastIdx) adapter.notifyItemChanged(lastIdx)
adapter.notifyItemChanged(curIdx) adapter.notifyItemChanged(curIdx)
// Selected index isn't logical slot ID directly, needs a conversion // Selected index isn't logical slot ID directly, needs a conversion
state.selectedLogicalSlot = adapter.slots[adapter.currentSelectedIdx].logicalSlotId adapter.slots[adapter.currentSelectedIdx].let {
state.imei = adapter.slots[adapter.currentSelectedIdx].imei state.selectedLogicalSlot = it.logicalSlotId
state.configuredAddresses = it.configuredAddresses
state.imei = it.imei
}
} }
fun bind(item: SlotInfo, idx: Int) { fun bind(item: SlotInfo, idx: Int) {

View file

@ -33,6 +33,7 @@ internal object PreferenceKeys {
val DEVELOPER_OPTIONS_ENABLED = booleanPreferencesKey("developer_options_enabled") val DEVELOPER_OPTIONS_ENABLED = booleanPreferencesKey("developer_options_enabled")
val UNFILTERED_PROFILE_LIST = booleanPreferencesKey("unfiltered_profile_list") val UNFILTERED_PROFILE_LIST = booleanPreferencesKey("unfiltered_profile_list")
val IGNORE_TLS_CERTIFICATE = booleanPreferencesKey("ignore_tls_certificate") val IGNORE_TLS_CERTIFICATE = booleanPreferencesKey("ignore_tls_certificate")
val DISCOVERY_PROFILE = booleanPreferencesKey("discovery")
val EUICC_MEMORY_RESET = booleanPreferencesKey("euicc_memory_reset") val EUICC_MEMORY_RESET = booleanPreferencesKey("euicc_memory_reset")
} }
@ -51,6 +52,7 @@ open class PreferenceRepository(private val context: Context) {
val developerOptionsEnabledFlow = bindFlow(PreferenceKeys.DEVELOPER_OPTIONS_ENABLED, false) val developerOptionsEnabledFlow = bindFlow(PreferenceKeys.DEVELOPER_OPTIONS_ENABLED, false)
val unfilteredProfileListFlow = bindFlow(PreferenceKeys.UNFILTERED_PROFILE_LIST, false) val unfilteredProfileListFlow = bindFlow(PreferenceKeys.UNFILTERED_PROFILE_LIST, false)
val ignoreTLSCertificateFlow = bindFlow(PreferenceKeys.IGNORE_TLS_CERTIFICATE, false) val ignoreTLSCertificateFlow = bindFlow(PreferenceKeys.IGNORE_TLS_CERTIFICATE, false)
val discoveryProfileFlow = bindFlow(PreferenceKeys.DISCOVERY_PROFILE, false)
val euiccMemoryResetFlow = bindFlow(PreferenceKeys.EUICC_MEMORY_RESET, false) val euiccMemoryResetFlow = bindFlow(PreferenceKeys.EUICC_MEMORY_RESET, false)
protected fun <T> bindFlow(key: Preferences.Key<T>, defaultValue: T): PreferenceFlowWrapper<T> = protected fun <T> bindFlow(key: Preferences.Key<T>, defaultValue: T): PreferenceFlowWrapper<T> =

View file

@ -93,13 +93,12 @@ inline fun <T> Bitmap.use(f: (Bitmap) -> T): T =
recycle() recycle()
} }
fun decodeQrFromBitmap(bmp: Bitmap): String? = fun decodeQrFromBitmap(bmp: Bitmap): String? {
runCatching { val pixels = IntArray(bmp.width * bmp.height)
val pixels = IntArray(bmp.width * bmp.height) bmp.getPixels(pixels, 0, bmp.width, 0, 0, bmp.width, bmp.height)
bmp.getPixels(pixels, 0, bmp.width, 0, 0, bmp.width, bmp.height)
val luminanceSource = RGBLuminanceSource(bmp.width, bmp.height, pixels) val luminanceSource = RGBLuminanceSource(bmp.width, bmp.height, pixels)
val binaryBmp = BinaryBitmap(HybridBinarizer(luminanceSource)) val binaryBmp = BinaryBitmap(HybridBinarizer(luminanceSource))
QRCodeReader().decode(binaryBmp).text return QRCodeReader().decode(binaryBmp).text
}.getOrNull() }

View file

@ -1,5 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp"> <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
<path android:fillColor="@android:color/white" android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/> android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z" />
</vector> </vector>

View file

@ -1,5 +1,10 @@
<vector android:height="24dp" android:tint="?attr/colorControlNormal" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportHeight="24" android:viewportWidth="24" android:width="24dp"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> android:height="24dp"
<path android:fillColor="@android:color/white" android:pathData="M22,16L22,4c0,-1.1 -0.9,-2 -2,-2L8,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2zM11,12l2.03,2.71L16,11l4,5L8,16l3,-4zM2,6v14c0,1.1 0.9,2 2,2h14v-2L4,20L4,6L2,6z"/> android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M22,16L22,4c0,-1.1 -0.9,-2 -2,-2L8,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2zM11,12l2.03,2.71L16,11l4,5L8,16l3,-4zM2,6v14c0,1.1 0.9,2 2,2h14v-2L4,20L4,6L2,6z" />
</vector> </vector>

View file

@ -1,7 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="?attr/colorControlNormal" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp"> <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
<path android:fillColor="@android:color/white" android:pathData="M5,5h2v3h10V5h2v6h2V5c0,-1.1 -0.9,-2 -2,-2h-4.18C14.4,1.84 13.3,1 12,1S9.6,1.84 9.18,3H5C3.9,3 3,3.9 3,5v14c0,1.1 0.9,2 2,2h5v-2H5V5zM12,3c0.55,0 1,0.45 1,1s-0.45,1 -1,1s-1,-0.45 -1,-1S11.45,3 12,3z"/> android:height="24dp"
android:tint="?attr/colorControlNormal"
<path android:fillColor="@android:color/white" android:pathData="M18.01,13l-1.42,1.41l1.58,1.58l-6.17,0l0,2l6.17,0l-1.58,1.59l1.42,1.41l3.99,-4z"/> android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M5,5h2v3h10V5h2v6h2V5c0,-1.1 -0.9,-2 -2,-2h-4.18C14.4,1.84 13.3,1 12,1S9.6,1.84 9.18,3H5C3.9,3 3,3.9 3,5v14c0,1.1 0.9,2 2,2h5v-2H5V5zM12,3c0.55,0 1,0.45 1,1s-0.45,1 -1,1s-1,-0.45 -1,-1S11.45,3 12,3z" />
<path
android:fillColor="@android:color/white"
android:pathData="M18.01,13l-1.42,1.41l1.58,1.58l-6.17,0l0,2l6.17,0l-1.58,1.59l1.42,1.41l3.99,-4z" />
</vector> </vector>

View file

@ -1,10 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24" android:viewportHeight="24">
android:tint="?attr/colorControlNormal"> <path
<path android:fillColor="@android:color/white"
android:fillColor="@android:color/white" android:pathData="M9.5,6.5v3h-3v-3H9.5M11,5H5v6h6V5L11,5zM9.5,14.5v3h-3v-3H9.5M11,13H5v6h6V13L11,13zM17.5,6.5v3h-3v-3H17.5M19,5h-6v6h6V5L19,5zM13,13h1.5v1.5H13V13zM14.5,14.5H16V16h-1.5V14.5zM16,13h1.5v1.5H16V13zM13,16h1.5v1.5H13V16zM14.5,17.5H16V19h-1.5V17.5zM16,16h1.5v1.5H16V16zM17.5,14.5H19V16h-1.5V14.5zM17.5,17.5H19V19h-1.5V17.5zM22,7h-2V4h-3V2h5V7zM22,22v-5h-2v3h-3v2H22zM2,22h5v-2H4v-3H2V22zM2,2v5h2V4h3V2H2z" />
android:pathData="M9.5,6.5v3h-3v-3H9.5M11,5H5v6h6V5L11,5zM9.5,14.5v3h-3v-3H9.5M11,13H5v6h6V13L11,13zM17.5,6.5v3h-3v-3H17.5M19,5h-6v6h6V5L19,5zM13,13h1.5v1.5H13V13zM14.5,14.5H16V16h-1.5V14.5zM16,13h1.5v1.5H16V13zM13,16h1.5v1.5H13V16zM14.5,17.5H16V19h-1.5V17.5zM16,16h1.5v1.5H16V16zM17.5,14.5H19V16h-1.5V14.5zM17.5,17.5H19V19h-1.5V17.5zM22,7h-2V4h-3V2h5V7zM22,22v-5h-2v3h-3v2H22zM2,22h5v-2H4v-3H2V22zM2,2v5h2V4h3V2H2z"/>
</vector> </vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="50"
android:viewportHeight="50">
<path
android:fillColor="@android:color/white"
android:pathData="M21,3a17,17 0,1 0,10 31l13,13 3,-3 -13,-13c2,-3 4,-7 4,-11 0,-9 -8,-17 -17,-17ZM21,5a15,15 0,1 1,0 30,15 15,0 0,1 0,-30Z" />
</vector>

View file

@ -83,6 +83,7 @@
<string name="download_wizard_method_gallery">Load a QR code from gallery</string> <string name="download_wizard_method_gallery">Load a QR code from gallery</string>
<string name="download_wizard_method_clipboard">Load from Clipboard</string> <string name="download_wizard_method_clipboard">Load from Clipboard</string>
<string name="download_wizard_method_manual">Enter manually</string> <string name="download_wizard_method_manual">Enter manually</string>
<string name="download_wizard_method_discovery">Discovery</string>
<string name="download_wizard_details">Input or confirm details for downloading your eSIM:</string> <string name="download_wizard_details">Input or confirm details for downloading your eSIM:</string>
<string name="download_wizard_progress">Downloading your eSIM…</string> <string name="download_wizard_progress">Downloading your eSIM…</string>
<string name="download_wizard_progress_step_preparing">Preparing</string> <string name="download_wizard_progress_step_preparing">Preparing</string>
@ -181,6 +182,8 @@
<string name="pref_advanced_logs">Logs</string> <string name="pref_advanced_logs">Logs</string>
<string name="pref_advanced_logs_desc">View recent debug logs of the application</string> <string name="pref_advanced_logs_desc">View recent debug logs of the application</string>
<string name="pref_developer">Developer Options</string> <string name="pref_developer">Developer Options</string>
<string name="pref_developer_discovery_profile">Discovery Profile</string>
<string name="pref_developer_discovery_profile_desc">Discover new profile and install them via SM-DS</string>
<string name="pref_developer_unfiltered_profile_list">Show unfiltered profile list</string> <string name="pref_developer_unfiltered_profile_list">Show unfiltered profile list</string>
<string name="pref_developer_unfiltered_profile_list_desc">Include non-production profiles in the list</string> <string name="pref_developer_unfiltered_profile_list_desc">Include non-production profiles in the list</string>
<string name="pref_developer_ignore_tls_certificate">Ignore SM-DP+ TLS certificate</string> <string name="pref_developer_ignore_tls_certificate">Ignore SM-DP+ TLS certificate</string>

View file

@ -57,6 +57,12 @@
app:title="@string/pref_developer" app:title="@string/pref_developer"
app:iconSpaceReserved="false"> app:iconSpaceReserved="false">
<CheckBoxPreference
app:iconSpaceReserved="false"
app:key="pref_developer_discovery_profile"
app:summary="@string/pref_developer_discovery_profile_desc"
app:title="@string/pref_developer_discovery_profile" />
<CheckBoxPreference <CheckBoxPreference
app:iconSpaceReserved="false" app:iconSpaceReserved="false"
app:key="pref_developer_unfiltered_profile_list" app:key="pref_developer_unfiltered_profile_list"

View file

@ -8,7 +8,8 @@ import im.angry.openeuicc.util.*
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import java.lang.IllegalArgumentException import java.lang.IllegalArgumentException
class PrivilegedEuiccChannelFactory(context: Context) : DefaultEuiccChannelFactory(context) { class PrivilegedEuiccChannelFactory(context: Context) : DefaultEuiccChannelFactory(context),
PrivilegedEuiccContextMarker {
private val tm by lazy { private val tm by lazy {
(context.applicationContext as OpenEuiccApplication).appContainer.telephonyManager (context.applicationContext as OpenEuiccApplication).appContainer.telephonyManager
} }
@ -22,7 +23,7 @@ class PrivilegedEuiccChannelFactory(context: Context) : DefaultEuiccChannelFacto
super.tryOpenEuiccChannel(port)?.let { return it } super.tryOpenEuiccChannel(port)?.let { return it }
} }
if (port.card.isEuicc || (context.preferenceRepository as PrivilegedPreferenceRepository).removableTelephonyManagerFlow.first()) { if (port.card.isEuicc || preferenceRepository.removableTelephonyManagerFlow.first()) {
Log.i( Log.i(
DefaultEuiccChannelManager.TAG, DefaultEuiccChannelManager.TAG,
"Trying TelephonyManager for slot ${port.card.physicalSlotIndex} port ${port.portIndex}" "Trying TelephonyManager for slot ${port.card.physicalSlotIndex} port ${port.portIndex}"

View file

@ -6,7 +6,7 @@ import androidx.preference.Preference
import im.angry.openeuicc.R import im.angry.openeuicc.R
import im.angry.openeuicc.util.* import im.angry.openeuicc.util.*
class PrivilegedSettingsFragment : SettingsFragment() { class PrivilegedSettingsFragment : SettingsFragment(), PrivilegedEuiccContextMarker {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
super.onCreatePreferences(savedInstanceState, rootKey) super.onCreatePreferences(savedInstanceState, rootKey)
addPreferencesFromResource(R.xml.pref_privileged_settings) addPreferencesFromResource(R.xml.pref_privileged_settings)
@ -21,7 +21,7 @@ class PrivilegedSettingsFragment : SettingsFragment() {
requirePreference<Preference>("pref_advanced_language").isVisible = false requirePreference<Preference>("pref_advanced_language").isVisible = false
// Force use TelephonyManager API // Force use TelephonyManager API
requirePreference<CheckBoxPreference>("pref_developer_tmapi_removable") requirePreference<CheckBoxPreference>("pref_developer_removable_telephony_manager")
.bindBooleanFlow((preferenceRepository as PrivilegedPreferenceRepository).removableTelephonyManagerFlow) .bindBooleanFlow(preferenceRepository.removableTelephonyManagerFlow)
} }
} }

View file

@ -5,10 +5,23 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.ServiceConnection import android.content.ServiceConnection
import android.os.IBinder import android.os.IBinder
import androidx.fragment.app.Fragment
import java.util.concurrent.Executors import java.util.concurrent.Executors
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
interface PrivilegedEuiccContextMarker {
val privilegedEuiccMarkerContext: Context
get() = when (this) {
is Context -> this
is Fragment -> requireContext()
else -> throw RuntimeException("PrivilegedEuiccContextMarker shall only be used on Fragments or UI types that derive from Context")
}
val preferenceRepository: PrivilegedPreferenceRepository
get() = privilegedEuiccMarkerContext.preferenceRepository as PrivilegedPreferenceRepository
}
suspend fun Context.bindServiceSuspended(intent: Intent, flags: Int): Pair<IBinder?, () -> Unit> = suspend fun Context.bindServiceSuspended(intent: Intent, flags: Int): Pair<IBinder?, () -> Unit> =
suspendCoroutine { cont -> suspendCoroutine { cont ->
var binder: IBinder? var binder: IBinder?

View file

@ -5,7 +5,7 @@
app:key="pref_developer_overlay"> app:key="pref_developer_overlay">
<CheckBoxPreference <CheckBoxPreference
app:iconSpaceReserved="false" app:iconSpaceReserved="false"
app:key="pref_developer_tmapi_removable" app:key="pref_developer_removable_telephony_manager"
app:summary="@string/pref_developer_telephony_manager_removable_desc" app:summary="@string/pref_developer_telephony_manager_removable_desc"
app:title="@string/pref_developer_telephony_manager_removable" /> app:title="@string/pref_developer_telephony_manager_removable" />
</PreferenceCategory> </PreferenceCategory>

View file

@ -0,0 +1,29 @@
package net.typeblog.lpac_jni
import android.util.Patterns
@Suppress("SpellCheckingInspection")
val invalidDPAddresses = setOf(
"testrootsmds.example.com",
"testrootsmds.gsma.com",
)
class EuiccConfiguredAddresses(defaultDPAddress: String?, rootDSAddress: String?) {
val defaultDPAddress: String? = defaultDPAddress.takeUnless(::isInvalidDPAddress)
val rootDSAddress = rootDSAddress.takeUnless(::isInvalidDSAddress)
val discoverable: Boolean
get() = !defaultDPAddress.isNullOrBlank() || !rootDSAddress.isNullOrBlank()
}
private fun isInvalidDPAddress(address: String?): Boolean {
if (address.isNullOrBlank()) return true
return !Patterns.DOMAIN_NAME.matcher(address).matches()
}
private fun isInvalidDSAddress(address: String?): Boolean {
if (address.isNullOrBlank()) return true
if (address in invalidDPAddresses) return true
if (Patterns.DOMAIN_NAME.matcher(address).matches()) return false
return false
}

View file

@ -12,6 +12,15 @@ interface LocalProfileAssistant {
val lastApduException: Exception?, val lastApduException: Exception?,
) : Exception("Failed to download profile") ) : Exception("Failed to download profile")
@Suppress("ArrayInDataClass")
data class ProfileDiscoveryException(
val lpaErrorReason: String,
val lastHttpResponse: HttpResponse?,
val lastHttpException: Exception?,
val lastApduResponse: ByteArray?,
val lastApduException: Exception?,
) : Exception("Failed to discovery profile")
class ProfileRenameException() : Exception("Failed to rename profile") class ProfileRenameException() : Exception("Failed to rename profile")
class ProfileNameTooLongException() : Exception("Profile name too long") class ProfileNameTooLongException() : Exception("Profile name too long")
class ProfileNameIsInvalidUTF8Exception() : Exception("Profile name is invalid UTF-8") class ProfileNameIsInvalidUTF8Exception() : Exception("Profile name is invalid UTF-8")
@ -30,6 +39,9 @@ interface LocalProfileAssistant {
*/ */
fun setEs10xMss(mss: Byte) fun setEs10xMss(mss: Byte)
// es10a
fun getEuiccConfiguredAddresses(): EuiccConfiguredAddresses
// All blocking functions in this class assume that they are executed on non-Main threads // All blocking functions in this class assume that they are executed on non-Main threads
// The IO context in Kotlin's coroutine library is recommended. // The IO context in Kotlin's coroutine library is recommended.
fun enableProfile(iccid: String, refresh: Boolean = true): Boolean fun enableProfile(iccid: String, refresh: Boolean = true): Boolean
@ -38,6 +50,7 @@ interface LocalProfileAssistant {
fun downloadProfile(smdp: String, matchingId: String?, imei: String?, fun downloadProfile(smdp: String, matchingId: String?, imei: String?,
confirmationCode: String?, callback: ProfileDownloadCallback) confirmationCode: String?, callback: ProfileDownloadCallback)
fun discoveryProfile(smds: String, imei: String?, callback: ProfileDiscoveryCallback)
fun deleteNotification(seqNumber: Long): Boolean fun deleteNotification(seqNumber: Long): Boolean
fun handleNotification(seqNumber: Long): Boolean fun handleNotification(seqNumber: Long): Boolean

View file

@ -29,6 +29,12 @@ internal object LpacJni {
external fun es10bListNotification(handle: Long): Long // A native pointer to a linked list. Handle with linked list-related methods below. May be 0 (null) external fun es10bListNotification(handle: Long): Long // A native pointer to a linked list. Handle with linked list-related methods below. May be 0 (null)
external fun es10bDeleteNotification(handle: Long, seqNumber: Long): Int external fun es10bDeleteNotification(handle: Long, seqNumber: Long): Int
// es10a
external fun es10aGetEuiccConfiguredAddresses(handle: Long): EuiccConfiguredAddresses
// es9p + es11
external fun discoveryProfile(handle: Long, address: String, imei: String?, callback: ProfileDiscoveryCallback): Int
// es9p + es10b // es9p + es10b
// We do not expose all of the functions because of tediousness :) // We do not expose all of the functions because of tediousness :)
external fun downloadProfile(handle: Long, smdp: String, matchingId: String?, imei: String?, external fun downloadProfile(handle: Long, smdp: String, matchingId: String?, imei: String?,

View file

@ -0,0 +1,5 @@
package net.typeblog.lpac_jni
interface ProfileDiscoveryCallback {
fun onDiscovered(hosts: Array<String>)
}

View file

@ -2,15 +2,14 @@ package net.typeblog.lpac_jni
interface ProfileDownloadCallback { interface ProfileDownloadCallback {
companion object { companion object {
fun lookupStateFromProgress(progress: Int): DownloadState = fun lookupStateFromProgress(progress: Int): DownloadState = when (progress) {
when (progress) { 0 -> DownloadState.Preparing
0 -> DownloadState.Preparing 20 -> DownloadState.Connecting
20 -> DownloadState.Connecting 40 -> DownloadState.Authenticating
40 -> DownloadState.Authenticating 60 -> DownloadState.Downloading
60 -> DownloadState.Downloading 80 -> DownloadState.Finalizing
80 -> DownloadState.Finalizing else -> throw IllegalArgumentException("Unknown state")
else -> throw IllegalArgumentException("Unknown state") }
}
} }
enum class DownloadState(val progress: Int) { enum class DownloadState(val progress: Int) {

View file

@ -9,6 +9,7 @@ import net.typeblog.lpac_jni.HttpInterface.HttpResponse
import net.typeblog.lpac_jni.LocalProfileAssistant import net.typeblog.lpac_jni.LocalProfileAssistant
import net.typeblog.lpac_jni.LocalProfileInfo import net.typeblog.lpac_jni.LocalProfileInfo
import net.typeblog.lpac_jni.LocalProfileNotification import net.typeblog.lpac_jni.LocalProfileNotification
import net.typeblog.lpac_jni.ProfileDiscoveryCallback
import net.typeblog.lpac_jni.ProfileDownloadCallback import net.typeblog.lpac_jni.ProfileDownloadCallback
import net.typeblog.lpac_jni.Version import net.typeblog.lpac_jni.Version
@ -93,6 +94,9 @@ class LocalProfileAssistantImpl(
LpacJni.euiccSetMss(contextHandle, mss) LpacJni.euiccSetMss(contextHandle, mss)
} }
override fun getEuiccConfiguredAddresses() =
LpacJni.es10aGetEuiccConfiguredAddresses(contextHandle)
override val valid: Boolean override val valid: Boolean
get() = !finalized && apduInterface.valid && try { get() = !finalized && apduInterface.valid && try {
// If we can read both eID and euiccInfo2 properly, we are likely looking at // If we can read both eID and euiccInfo2 properly, we are likely looking at
@ -212,21 +216,32 @@ class LocalProfileAssistantImpl(
callback callback
) )
if (res != 0) { if (res == 0) return
// Construct the error now to store any error information we _can_ access // Construct the error now to store any error information we _can_ access
val err = LocalProfileAssistant.ProfileDownloadException( val err = LocalProfileAssistant.ProfileDownloadException(
lpaErrorReason = LpacJni.downloadErrCodeToString(-res), lpaErrorReason = LpacJni.downloadErrCodeToString(-res),
httpInterface.lastHttpResponse, httpInterface.lastHttpResponse,
httpInterface.lastHttpException, httpInterface.lastHttpException,
apduInterface.lastApduResponse, apduInterface.lastApduResponse,
apduInterface.lastApduException, apduInterface.lastApduException,
) )
// Cancel sessions if possible. This will overwrite recorded errors from HTTP and APDU interfaces. // Cancel sessions if possible. This will overwrite recorded errors from HTTP and APDU interfaces.
LpacJni.cancelSessions(contextHandle) LpacJni.cancelSessions(contextHandle)
throw err throw err
} }
override fun discoveryProfile(smds: String, imei: String?, callback: ProfileDiscoveryCallback) {
val res = LpacJni.discoveryProfile(contextHandle, smds, imei, callback)
if (res == 0) return
throw LocalProfileAssistant.ProfileDiscoveryException(
lpaErrorReason = LpacJni.downloadErrCodeToString(-res),
httpInterface.lastHttpResponse,
httpInterface.lastHttpException,
apduInterface.lastApduResponse,
apduInterface.lastApduException,
)
} }
@Synchronized @Synchronized

View file

@ -0,0 +1,127 @@
#include "lpac-notifications.h"
#include <euicc/es10a.h>
#include <euicc/es10b.h>
#include <euicc/es9p.h>
#include <string.h>
#include <malloc.h>
#include <syslog.h>
#define EUICC_CONFIGURED_ADDRESSES_CLASS "net/typeblog/lpac_jni/EuiccConfiguredAddresses"
jclass euicc_configured_addresses_class;
jmethodID euicc_configured_addresses_constructor;
jmethodID on_discovered;
#define DISCOVERY_CALLBACK_CLASS "net/typeblog/lpac_jni/ProfileDiscoveryCallback"
#define STRING_CLASS "java/lang/String"
static jobject bind_static_field(JNIEnv *env, jclass clazz, const char *name, const char *sig) {
jfieldID field = (*env)->GetStaticFieldID(env, clazz, name, sig);
jobject bound = (*env)->GetStaticObjectField(env, clazz, field);
return (*env)->NewGlobalRef(env, bound);
}
void lpac_discovery_init() {
LPAC_JNI_SETUP_ENV;
jclass download_callback_class = (*env)->FindClass(env, DISCOVERY_CALLBACK_CLASS);
on_discovered = (*env)->GetMethodID(env, download_callback_class, "onDiscovered",
"([L" STRING_CLASS ";)V");
euicc_configured_addresses_class = (*env)->FindClass(env, EUICC_CONFIGURED_ADDRESSES_CLASS);
euicc_configured_addresses_class = (*env)->NewGlobalRef(env, euicc_configured_addresses_class);
euicc_configured_addresses_constructor = (*env)->GetMethodID(
env, euicc_configured_addresses_class, "<init>",
"(L" STRING_CLASS ";L" STRING_CLASS ";)V");
}
JNIEXPORT jobject JNICALL
Java_net_typeblog_lpac_1jni_LpacJni_es10aGetEuiccConfiguredAddresses(
JNIEnv *env,
__attribute__((unused)) jobject thiz,
jlong handle
) {
struct euicc_ctx *ctx = (struct euicc_ctx *) handle;
struct es10a_euicc_configured_addresses addresses;
jobject ret = NULL;
if (es10a_get_euicc_configured_addresses(ctx, &addresses) == 0) {
jstring default_dp_address = toJString(env, addresses.defaultDpAddress);
jstring root_ds_address = toJString(env, addresses.rootDsAddress);
ret = (*env)->NewObject(env, euicc_configured_addresses_class,
euicc_configured_addresses_constructor,
default_dp_address, root_ds_address);
}
es10a_euicc_configured_addresses_free(&addresses);
return ret;
}
JNIEXPORT jint JNICALL
Java_net_typeblog_lpac_1jni_LpacJni_discoveryProfile(
JNIEnv *env,
__attribute__((unused)) jobject thiz,
jlong handle,
jstring address,
jstring imei,
jobject callback
) {
struct euicc_ctx *ctx = (struct euicc_ctx *) handle;
const char *_address = (*env)->GetStringUTFChars(env, address, NULL);
const char *_imei = NULL;
if (imei != NULL) {
_imei = (*env)->GetStringUTFChars(env, address, NULL);
}
ctx->http.server_address = _address;
char **smdp_list = NULL;
jobjectArray addresses = NULL;
int ret = -1;
ret = es10b_get_euicc_challenge_and_info(ctx);
syslog(LOG_INFO, "es10b_get_euicc_challenge_and_info %d", ret);
if (ret < 0) {
ret = -ES10B_ERROR_REASON_UNDEFINED;
goto out;
}
ret = es9p_initiate_authentication(ctx);
syslog(LOG_INFO, "es9p_initiate_authentication %d", ret);
if (ret < 0) {
ret = -ES10B_ERROR_REASON_UNDEFINED;
goto out;
}
ret = es10b_authenticate_server(ctx, NULL, _imei);
syslog(LOG_INFO, "es10b_authenticate_server %d", ret);
if (ret < 0) {
ret = -ES10B_ERROR_REASON_UNDEFINED;
goto out;
}
ret = es11_authenticate_client(ctx, &smdp_list);
if (ret < 0) {
ret = -ES10B_ERROR_REASON_UNDEFINED;
goto out;
}
jsize n = 0;
for (n = 0; smdp_list[n] != NULL; n++) continue;
addresses = (*env)->NewObjectArray(env, n, string_class, NULL);
for (jsize index = 0; index < n; index++) {
jstring element = toJString(env, smdp_list[index]);
(*env)->SetObjectArrayElement(env, addresses, index, element);
}
(*env)->CallVoidMethod(env, callback, on_discovered, addresses);
out:
if (_imei != NULL) (*env)->ReleaseStringUTFChars(env, imei, _imei);
(*env)->ReleaseStringUTFChars(env, address, _address);
es11_smdp_list_free_all(smdp_list);
return ret;
}

View file

@ -0,0 +1,6 @@
#pragma once
#include <jni.h>
#include "lpac-jni.h"
void lpac_discovery_init();

View file

@ -11,51 +11,38 @@ jobject download_state_authenticating;
jobject download_state_downloading; jobject download_state_downloading;
jobject download_state_finalizing; jobject download_state_finalizing;
jmethodID on_state_update; jmethodID on_discovered;
#define DOWNLOAD_CALLBACK_CLASS "net/typeblog/lpac_jni/ProfileDownloadCallback"
#define DOWNLOAD_STATE_CLASS DOWNLOAD_CALLBACK_CLASS "$DownloadState"
static jobject bind_static_field(JNIEnv *env, jclass clazz, const char *name, const char *sig) {
jfieldID field = (*env)->GetStaticFieldID(env, clazz, name, sig);
jobject bound = (*env)->GetStaticObjectField(env, clazz, field);
return (*env)->NewGlobalRef(env, bound);
}
#define BIND_DOWNLOAD_STATE_STATIC_FIELD(NAME, FIELD) \
NAME = bind_static_field(env, download_state_class, FIELD, "L" DOWNLOAD_STATE_CLASS ";")
void lpac_download_init() { void lpac_download_init() {
LPAC_JNI_SETUP_ENV; LPAC_JNI_SETUP_ENV;
jclass download_state_class = (*env)->FindClass(env, jclass download_state_class = (*env)->FindClass(env, DOWNLOAD_STATE_CLASS);
"net/typeblog/lpac_jni/ProfileDownloadCallback$DownloadState");
jfieldID download_state_preparing_field = (*env)->GetStaticFieldID(env, download_state_class,
"Preparing",
"Lnet/typeblog/lpac_jni/ProfileDownloadCallback$DownloadState;");
download_state_preparing = (*env)->GetStaticObjectField(env, download_state_class,
download_state_preparing_field);
download_state_preparing = (*env)->NewGlobalRef(env, download_state_preparing);
jfieldID download_state_connecting_field = (*env)->GetStaticFieldID(env, download_state_class,
"Connecting",
"Lnet/typeblog/lpac_jni/ProfileDownloadCallback$DownloadState;");
download_state_connecting = (*env)->GetStaticObjectField(env, download_state_class,
download_state_connecting_field);
download_state_connecting = (*env)->NewGlobalRef(env, download_state_connecting);
jfieldID download_state_authenticating_field = (*env)->GetStaticFieldID(env,
download_state_class,
"Authenticating",
"Lnet/typeblog/lpac_jni/ProfileDownloadCallback$DownloadState;");
download_state_authenticating = (*env)->GetStaticObjectField(env, download_state_class,
download_state_authenticating_field);
download_state_authenticating = (*env)->NewGlobalRef(env, download_state_authenticating);
jfieldID download_state_downloading_field = (*env)->GetStaticFieldID(env, download_state_class,
"Downloading",
"Lnet/typeblog/lpac_jni/ProfileDownloadCallback$DownloadState;");
download_state_downloading = (*env)->GetStaticObjectField(env, download_state_class,
download_state_downloading_field);
download_state_downloading = (*env)->NewGlobalRef(env, download_state_downloading);
jfieldID download_state_finalizng_field = (*env)->GetStaticFieldID(env, download_state_class,
"Finalizing",
"Lnet/typeblog/lpac_jni/ProfileDownloadCallback$DownloadState;");
download_state_finalizing = (*env)->GetStaticObjectField(env, download_state_class,
download_state_finalizng_field);
download_state_finalizing = (*env)->NewGlobalRef(env, download_state_finalizing);
jclass download_callback_class = (*env)->FindClass(env, BIND_DOWNLOAD_STATE_STATIC_FIELD(download_state_preparing, "Preparing");
"net/typeblog/lpac_jni/ProfileDownloadCallback"); BIND_DOWNLOAD_STATE_STATIC_FIELD(download_state_connecting, "Connecting");
on_state_update = (*env)->GetMethodID(env, download_callback_class, "onStateUpdate", BIND_DOWNLOAD_STATE_STATIC_FIELD(download_state_authenticating, "Authenticating");
"(Lnet/typeblog/lpac_jni/ProfileDownloadCallback$DownloadState;)V"); BIND_DOWNLOAD_STATE_STATIC_FIELD(download_state_downloading, "Downloading");
BIND_DOWNLOAD_STATE_STATIC_FIELD(download_state_finalizing, "Finalizing");
jclass download_callback_class = (*env)->FindClass(env, DOWNLOAD_CALLBACK_CLASS);
on_discovered = (*env)->GetMethodID(
env, download_callback_class, "onStateUpdate", "(L" DOWNLOAD_STATE_CLASS ";)V");
} }
#undef BIND_DOWNLOAD_STATE_STATIC_FIELD
JNIEXPORT jint JNICALL JNIEXPORT jint JNICALL
Java_net_typeblog_lpac_1jni_LpacJni_downloadProfile(JNIEnv *env, jobject thiz, jlong handle, Java_net_typeblog_lpac_1jni_LpacJni_downloadProfile(JNIEnv *env, jobject thiz, jlong handle,
jstring smdp, jstring matching_id, jstring smdp, jstring matching_id,
@ -79,7 +66,7 @@ Java_net_typeblog_lpac_1jni_LpacJni_downloadProfile(JNIEnv *env, jobject thiz, j
ctx->http.server_address = _smdp; ctx->http.server_address = _smdp;
(*env)->CallVoidMethod(env, callback, on_state_update, download_state_preparing); (*env)->CallVoidMethod(env, callback, on_discovered, download_state_preparing);
ret = es10b_get_euicc_challenge_and_info(ctx); ret = es10b_get_euicc_challenge_and_info(ctx);
syslog(LOG_INFO, "es10b_get_euicc_challenge_and_info %d", ret); syslog(LOG_INFO, "es10b_get_euicc_challenge_and_info %d", ret);
if (ret < 0) { if (ret < 0) {
@ -87,7 +74,7 @@ Java_net_typeblog_lpac_1jni_LpacJni_downloadProfile(JNIEnv *env, jobject thiz, j
goto out; goto out;
} }
(*env)->CallVoidMethod(env, callback, on_state_update, download_state_connecting); (*env)->CallVoidMethod(env, callback, on_discovered, download_state_connecting);
ret = es9p_initiate_authentication(ctx); ret = es9p_initiate_authentication(ctx);
syslog(LOG_INFO, "es9p_initiate_authentication %d", ret); syslog(LOG_INFO, "es9p_initiate_authentication %d", ret);
if (ret < 0) { if (ret < 0) {
@ -95,7 +82,7 @@ Java_net_typeblog_lpac_1jni_LpacJni_downloadProfile(JNIEnv *env, jobject thiz, j
goto out; goto out;
} }
(*env)->CallVoidMethod(env, callback, on_state_update, download_state_authenticating); (*env)->CallVoidMethod(env, callback, on_discovered, download_state_authenticating);
ret = es10b_authenticate_server(ctx, _matching_id, _imei); ret = es10b_authenticate_server(ctx, _matching_id, _imei);
syslog(LOG_INFO, "es10b_authenticate_server %d", ret); syslog(LOG_INFO, "es10b_authenticate_server %d", ret);
if (ret < 0) { if (ret < 0) {
@ -109,7 +96,7 @@ Java_net_typeblog_lpac_1jni_LpacJni_downloadProfile(JNIEnv *env, jobject thiz, j
goto out; goto out;
} }
(*env)->CallVoidMethod(env, callback, on_state_update, download_state_downloading); (*env)->CallVoidMethod(env, callback, on_discovered, download_state_downloading);
ret = es10b_prepare_download(ctx, _confirmation_code); ret = es10b_prepare_download(ctx, _confirmation_code);
syslog(LOG_INFO, "es10b_prepare_download %d", ret); syslog(LOG_INFO, "es10b_prepare_download %d", ret);
if (ret < 0) { if (ret < 0) {
@ -121,7 +108,7 @@ Java_net_typeblog_lpac_1jni_LpacJni_downloadProfile(JNIEnv *env, jobject thiz, j
if (ret < 0) if (ret < 0)
goto out; goto out;
(*env)->CallVoidMethod(env, callback, on_state_update, download_state_finalizing); (*env)->CallVoidMethod(env, callback, on_discovered, download_state_finalizing);
ret = es10b_load_bound_profile_package(ctx, &es10b_load_bound_profile_package_result); ret = es10b_load_bound_profile_package(ctx, &es10b_load_bound_profile_package_result);
syslog(LOG_INFO, "es10b_load_bound_profile_package %d, reason %d", ret, es10b_load_bound_profile_package_result.errorReason); syslog(LOG_INFO, "es10b_load_bound_profile_package %d, reason %d", ret, es10b_load_bound_profile_package_result.errorReason);
if (ret < 0) { if (ret < 0) {

View file

@ -8,6 +8,7 @@
#include "lpac-jni.h" #include "lpac-jni.h"
#include "lpac-download.h" #include "lpac-download.h"
#include "lpac-notifications.h" #include "lpac-notifications.h"
#include "lpac-discovery.h"
#include "interface-wrapper.h" #include "interface-wrapper.h"
JavaVM *jvm = NULL; JavaVM *jvm = NULL;
@ -21,6 +22,7 @@ jint JNI_OnLoad(JavaVM *vm, void *reserved) {
jvm = vm; jvm = vm;
interface_wrapper_init(); interface_wrapper_init();
lpac_download_init(); lpac_download_init();
lpac_discovery_init();
LPAC_JNI_SETUP_ENV; LPAC_JNI_SETUP_ENV;
string_class = (*env)->FindClass(env, "java/lang/String"); string_class = (*env)->FindClass(env, "java/lang/String");