Compare commits

...

5 commits

Author SHA1 Message Date
5517b7dcd1 fix: Name clash with OpenEuiccContextMarker::context 2024-02-04 20:43:59 -05:00
dedf633ce4 refactor: Rename EuiccFragmentMarker 2024-02-04 20:42:19 -05:00
5514171070 refactor: do not use extension functions for OpenEuiccContextMarker
we can just define them internally
2024-02-04 20:32:17 -05:00
1c8918e7f0 Make OpenEuiccUIContextMarker usable for services as well 2024-02-04 20:29:57 -05:00
632b6b4931 refactor: Create OpenEuiccUIContextMarker to facilitate easy access to application-global singletons
yes yes yes we should be using dependency injection but let's keep it
simple with AOSP building...
2024-02-04 20:26:54 -05:00
15 changed files with 187 additions and 66 deletions

117
.idea/codeStyles/Project.xml generated Normal file
View file

@ -0,0 +1,117 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
</code_scheme>
</component>

5
.idea/codeStyles/codeStyleConfig.xml generated Normal file
View file

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

View file

@ -8,27 +8,27 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
class DirectProfileDownloadActivity : AppCompatActivity(), SlotSelectFragment.SlotSelectedListener { class DirectProfileDownloadActivity : AppCompatActivity(), SlotSelectFragment.SlotSelectedListener, OpenEuiccContextMarker {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
lifecycleScope.launch { lifecycleScope.launch {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
openEuiccApplication.euiccChannelManager.enumerateEuiccChannels() euiccChannelManager.enumerateEuiccChannels()
} }
val knownChannels = openEuiccApplication.euiccChannelManager.knownChannels
when { when {
knownChannels.isEmpty() -> { euiccChannelManager.knownChannels.isEmpty() -> {
finish() finish()
} }
knownChannels.hasMultipleChips -> { euiccChannelManager.knownChannels.hasMultipleChips -> {
SlotSelectFragment.newInstance() SlotSelectFragment.newInstance()
.show(supportFragmentManager, SlotSelectFragment.TAG) .show(supportFragmentManager, SlotSelectFragment.TAG)
} }
else -> { else -> {
// If the device has only one eSIM "chip" (but may be mapped to multiple slots), // If the device has only one eSIM "chip" (but may be mapped to multiple slots),
// we can skip the slot selection dialog since there is only one chip to save to. // we can skip the slot selection dialog since there is only one chip to save to.
onSlotSelected(knownChannels[0].slotId, knownChannels[0].portId) onSlotSelected(euiccChannelManager.knownChannels[0].slotId,
euiccChannelManager.knownChannels[0].portId)
} }
} }
} }

View file

@ -31,7 +31,8 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.lang.Exception import java.lang.Exception
open class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesChangedListener { open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
EuiccChannelFragmentMarker {
companion object { companion object {
const val TAG = "EuiccManagementFragment" const val TAG = "EuiccManagementFragment"

View file

@ -20,7 +20,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
open class MainActivity : AppCompatActivity() { open class MainActivity : AppCompatActivity(), OpenEuiccContextMarker {
companion object { companion object {
const val TAG = "MainActivity" const val TAG = "MainActivity"
} }
@ -43,9 +43,9 @@ open class MainActivity : AppCompatActivity() {
noEuiccPlaceholder = findViewById(R.id.no_euicc_placeholder) noEuiccPlaceholder = findViewById(R.id.no_euicc_placeholder)
tm = openEuiccApplication.telephonyManager tm = telephonyManager
manager = openEuiccApplication.euiccChannelManager manager = euiccChannelManager
spinnerAdapter = ArrayAdapter<String>(this, R.layout.spinner_item) spinnerAdapter = ArrayAdapter<String>(this, R.layout.spinner_item)

View file

@ -19,16 +19,15 @@ import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import im.angry.openeuicc.OpenEuiccApplication
import im.angry.openeuicc.common.R import im.angry.openeuicc.common.R
import im.angry.openeuicc.core.EuiccChannel import im.angry.openeuicc.core.EuiccChannel
import im.angry.openeuicc.util.displayName 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 kotlinx.coroutines.withContext
import net.typeblog.lpac_jni.LocalProfileNotification import net.typeblog.lpac_jni.LocalProfileNotification
class NotificationsActivity: AppCompatActivity() { class NotificationsActivity: AppCompatActivity(), OpenEuiccContextMarker {
private lateinit var swipeRefresh: SwipeRefreshLayout private lateinit var swipeRefresh: SwipeRefreshLayout
private lateinit var notificationList: RecyclerView private lateinit var notificationList: RecyclerView
private val notificationAdapter = NotificationAdapter() private val notificationAdapter = NotificationAdapter()
@ -41,7 +40,7 @@ class NotificationsActivity: AppCompatActivity() {
setSupportActionBar(findViewById(R.id.toolbar)) setSupportActionBar(findViewById(R.id.toolbar))
supportActionBar!!.setDisplayHomeAsUpEnabled(true) supportActionBar!!.setDisplayHomeAsUpEnabled(true)
euiccChannel = (application as OpenEuiccApplication).euiccChannelManager euiccChannel = euiccChannelManager
.findEuiccChannelBySlotBlocking(intent.getIntExtra("logicalSlotId", 0))!! .findEuiccChannelBySlotBlocking(intent.getIntExtra("logicalSlotId", 0))!!
swipeRefresh = findViewById(R.id.swipe_refresh) swipeRefresh = findViewById(R.id.swipe_refresh)

View file

@ -7,15 +7,12 @@ import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import im.angry.openeuicc.common.R import im.angry.openeuicc.common.R
import im.angry.openeuicc.util.preferenceRepository import im.angry.openeuicc.util.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import net.typeblog.lpac_jni.LocalProfileNotification
import java.lang.Exception import java.lang.Exception
class ProfileDeleteFragment : DialogFragment(), EuiccFragmentMarker { class ProfileDeleteFragment : DialogFragment(), EuiccChannelFragmentMarker {
companion object { companion object {
const val TAG = "ProfileDeleteFragment" const val TAG = "ProfileDeleteFragment"

View file

@ -17,9 +17,7 @@ import com.google.android.material.textfield.TextInputLayout
import com.journeyapps.barcodescanner.ScanContract import com.journeyapps.barcodescanner.ScanContract
import com.journeyapps.barcodescanner.ScanOptions import com.journeyapps.barcodescanner.ScanOptions
import im.angry.openeuicc.common.R import im.angry.openeuicc.common.R
import im.angry.openeuicc.util.openEuiccApplication import im.angry.openeuicc.util.*
import im.angry.openeuicc.util.preferenceRepository
import im.angry.openeuicc.util.setWidthPercent
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -27,7 +25,8 @@ import kotlinx.coroutines.withContext
import net.typeblog.lpac_jni.ProfileDownloadCallback import net.typeblog.lpac_jni.ProfileDownloadCallback
import kotlin.Exception import kotlin.Exception
class ProfileDownloadFragment : BaseMaterialDialogFragment(), EuiccFragmentMarker, Toolbar.OnMenuItemClickListener { class ProfileDownloadFragment : BaseMaterialDialogFragment(),
Toolbar.OnMenuItemClickListener, EuiccChannelFragmentMarker {
companion object { companion object {
const val TAG = "ProfileDownloadFragment" const val TAG = "ProfileDownloadFragment"

View file

@ -6,21 +6,20 @@ import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.Window
import android.widget.ProgressBar import android.widget.ProgressBar
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import im.angry.openeuicc.common.R import im.angry.openeuicc.common.R
import im.angry.openeuicc.util.setWidthPercent 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 kotlinx.coroutines.withContext
import java.lang.Exception import java.lang.Exception
import java.lang.RuntimeException import java.lang.RuntimeException
class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccFragmentMarker { class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragmentMarker {
companion object { companion object {
const val TAG = "ProfileRenameFragment" const val TAG = "ProfileRenameFragment"

View file

@ -10,10 +10,9 @@ import android.widget.Spinner
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import im.angry.openeuicc.common.R import im.angry.openeuicc.common.R
import im.angry.openeuicc.core.EuiccChannel import im.angry.openeuicc.core.EuiccChannel
import im.angry.openeuicc.util.openEuiccApplication import im.angry.openeuicc.util.*
import im.angry.openeuicc.util.setWidthPercent
class SlotSelectFragment : BaseMaterialDialogFragment() { class SlotSelectFragment : BaseMaterialDialogFragment(), OpenEuiccContextMarker {
companion object { companion object {
const val TAG = "SlotSelectFragment" const val TAG = "SlotSelectFragment"
@ -30,7 +29,7 @@ class SlotSelectFragment : BaseMaterialDialogFragment() {
private lateinit var toolbar: Toolbar private lateinit var toolbar: Toolbar
private lateinit var spinner: Spinner private lateinit var spinner: Spinner
private val channels: List<EuiccChannel> by lazy { private val channels: List<EuiccChannel> by lazy {
openEuiccApplication.euiccChannelManager.knownChannels.sortedBy { it.logicalSlotId } euiccChannelManager.knownChannels.sortedBy { it.logicalSlotId }
} }
override fun onCreateView( override fun onCreateView(

View file

@ -1,14 +1,15 @@
package im.angry.openeuicc.ui package im.angry.openeuicc.util
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import im.angry.openeuicc.core.EuiccChannelManager
import im.angry.openeuicc.core.EuiccChannel import im.angry.openeuicc.core.EuiccChannel
import im.angry.openeuicc.util.openEuiccApplication
interface EuiccFragmentMarker interface EuiccChannelFragmentMarker: OpenEuiccContextMarker
fun <T> newInstanceEuicc(clazz: Class<T>, slotId: Int, portId: Int, addArguments: Bundle.() -> Unit = {}): T where T: Fragment, T: EuiccFragmentMarker { // We must use extension functions because there is no way to add bounds to the type of "self"
// in the definition of an interface, so the only way is to limit where the extension functions
// can be applied.
fun <T> newInstanceEuicc(clazz: Class<T>, slotId: Int, portId: Int, addArguments: Bundle.() -> Unit = {}): T where T: Fragment, T: EuiccChannelFragmentMarker {
val instance = clazz.newInstance() val instance = clazz.newInstance()
instance.arguments = Bundle().apply { instance.arguments = Bundle().apply {
putInt("slotId", slotId) putInt("slotId", slotId)
@ -18,15 +19,12 @@ fun <T> newInstanceEuicc(clazz: Class<T>, slotId: Int, portId: Int, addArguments
return instance return instance
} }
val <T> T.slotId: Int where T: Fragment, T: EuiccFragmentMarker val <T> T.slotId: Int where T: Fragment, T: EuiccChannelFragmentMarker
get() = requireArguments().getInt("slotId") get() = requireArguments().getInt("slotId")
val <T> T.portId: Int where T: Fragment, T: EuiccFragmentMarker val <T> T.portId: Int where T: Fragment, T: EuiccChannelFragmentMarker
get() = requireArguments().getInt("portId") get() = requireArguments().getInt("portId")
val <T> T.euiccChannelManager: EuiccChannelManager where T: Fragment, T: EuiccFragmentMarker val <T> T.channel: EuiccChannel where T: Fragment, T: EuiccChannelFragmentMarker
get() = openEuiccApplication.euiccChannelManager
val <T> T.channel: EuiccChannel where T: Fragment, T: EuiccFragmentMarker
get() = get() =
euiccChannelManager.findEuiccChannelByPortBlocking(slotId, portId)!! euiccChannelManager.findEuiccChannelByPortBlocking(slotId, portId)!!

View file

@ -1,18 +1,9 @@
package im.angry.openeuicc.util package im.angry.openeuicc.util
import android.app.Activity
import android.content.res.Resources import android.content.res.Resources
import android.graphics.Rect import android.graphics.Rect
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import im.angry.openeuicc.OpenEuiccApplication
val Activity.openEuiccApplication: OpenEuiccApplication
get() = application as OpenEuiccApplication
val Fragment.openEuiccApplication: OpenEuiccApplication
get() = requireActivity().openEuiccApplication
// Source: <https://stackoverflow.com/questions/12478520/how-to-set-dialogfragments-width-and-height> // Source: <https://stackoverflow.com/questions/12478520/how-to-set-dialogfragments-width-and-height>
/** /**

View file

@ -3,7 +3,11 @@ package im.angry.openeuicc.util
import android.content.Context import android.content.Context
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.se.omapi.SEService import android.se.omapi.SEService
import android.telephony.TelephonyManager
import androidx.fragment.app.Fragment
import im.angry.openeuicc.OpenEuiccApplication
import im.angry.openeuicc.core.EuiccChannel import im.angry.openeuicc.core.EuiccChannel
import im.angry.openeuicc.core.EuiccChannelManager
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
@ -22,6 +26,24 @@ val Context.selfAppVersion: String
throw RuntimeException(e) throw RuntimeException(e)
} }
interface OpenEuiccContextMarker {
val openEuiccMarkerContext: Context
get() = when (this) {
is Context -> this
is Fragment -> requireContext()
else -> throw RuntimeException("OpenEuiccUIContextMarker shall only be used on Fragments or UI types that derive from Context")
}
val openEuiccApplication: OpenEuiccApplication
get() = openEuiccMarkerContext.applicationContext as OpenEuiccApplication
val euiccChannelManager: EuiccChannelManager
get() = openEuiccApplication.euiccChannelManager
val telephonyManager: TelephonyManager
get() = openEuiccApplication.telephonyManager
}
val LocalProfileInfo.isEnabled: Boolean val LocalProfileInfo.isEnabled: Boolean
get() = state == LocalProfileInfo.State.Enabled get() = state == LocalProfileInfo.State.Enabled

View file

@ -6,30 +6,23 @@ import android.telephony.euicc.DownloadableSubscription
import android.telephony.euicc.EuiccInfo import android.telephony.euicc.EuiccInfo
import android.util.Log import android.util.Log
import net.typeblog.lpac_jni.LocalProfileInfo import net.typeblog.lpac_jni.LocalProfileInfo
import im.angry.openeuicc.OpenEuiccApplication
import im.angry.openeuicc.core.EuiccChannel import im.angry.openeuicc.core.EuiccChannel
import im.angry.openeuicc.util.* import im.angry.openeuicc.util.*
import java.lang.IllegalStateException import java.lang.IllegalStateException
class OpenEuiccService : EuiccService() { class OpenEuiccService : EuiccService(), OpenEuiccContextMarker {
companion object { companion object {
const val TAG = "OpenEuiccService" const val TAG = "OpenEuiccService"
} }
private val openEuiccApplication
get() = application as OpenEuiccApplication
private fun findChannel(physicalSlotId: Int): EuiccChannel? = private fun findChannel(physicalSlotId: Int): EuiccChannel? =
openEuiccApplication.euiccChannelManager euiccChannelManager.findEuiccChannelByPhysicalSlotBlocking(physicalSlotId)
.findEuiccChannelByPhysicalSlotBlocking(physicalSlotId)
private fun findChannel(slotId: Int, portId: Int): EuiccChannel? = private fun findChannel(slotId: Int, portId: Int): EuiccChannel? =
openEuiccApplication.euiccChannelManager euiccChannelManager.findEuiccChannelByPortBlocking(slotId, portId)
.findEuiccChannelByPortBlocking(slotId, portId)
private fun findAllChannels(physicalSlotId: Int): List<EuiccChannel>? = private fun findAllChannels(physicalSlotId: Int): List<EuiccChannel>? =
openEuiccApplication.euiccChannelManager euiccChannelManager.findAllEuiccChannelsByPhysicalSlotBlocking(physicalSlotId)
.findAllEuiccChannelsByPhysicalSlotBlocking(physicalSlotId)
override fun onGetEid(slotId: Int): String? = override fun onGetEid(slotId: Int): String? =
findChannel(slotId)?.lpa?.eID findChannel(slotId)?.lpa?.eID
@ -41,7 +34,7 @@ class OpenEuiccService : EuiccService() {
lpa.profiles.any { it.iccid == iccid } lpa.profiles.any { it.iccid == iccid }
private fun ensurePortIsMapped(slotId: Int, portId: Int) { private fun ensurePortIsMapped(slotId: Int, portId: Int) {
val mappings = openEuiccApplication.telephonyManager.simSlotMapping.toMutableList() val mappings = telephonyManager.simSlotMapping.toMutableList()
mappings.firstOrNull { it.physicalSlotIndex == slotId && it.portIndex == portId }?.let { mappings.firstOrNull { it.physicalSlotIndex == slotId && it.portIndex == portId }?.let {
throw IllegalStateException("Slot $slotId port $portId has already been mapped") throw IllegalStateException("Slot $slotId port $portId has already been mapped")
@ -57,14 +50,14 @@ class OpenEuiccService : EuiccService() {
} }
try { try {
openEuiccApplication.telephonyManager.simSlotMapping = mappings telephonyManager.simSlotMapping = mappings
return return
} catch (_: Exception) { } catch (_: Exception) {
} }
// Sometimes hardware supports one ordering but not the reverse // Sometimes hardware supports one ordering but not the reverse
openEuiccApplication.telephonyManager.simSlotMapping = mappings.reversed() telephonyManager.simSlotMapping = mappings.reversed()
} }
private fun <T> retryWithTimeout(timeoutMillis: Int, backoff: Int = 1000, f: () -> T?): T? { private fun <T> retryWithTimeout(timeoutMillis: Int, backoff: Int = 1000, f: () -> T?): T? {
@ -233,7 +226,7 @@ class OpenEuiccService : EuiccService() {
} catch (e: Exception) { } catch (e: Exception) {
return RESULT_FIRST_USER return RESULT_FIRST_USER
} finally { } finally {
openEuiccApplication.euiccChannelManager.invalidate() euiccChannelManager.invalidate()
} }
} }

View file

@ -26,7 +26,8 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
class SlotMappingFragment: BaseMaterialDialogFragment(), OnMenuItemClickListener { class SlotMappingFragment: BaseMaterialDialogFragment(),
OnMenuItemClickListener, OpenEuiccContextMarker {
companion object { companion object {
const val TAG = "SlotMappingFragment" const val TAG = "SlotMappingFragment"
} }