[6/n] Show special footer for SIM cards with MEP support

This commit is contained in:
Peter Cai 2023-12-16 21:40:08 -05:00
parent e708deea7c
commit 81b61c76c4
7 changed files with 142 additions and 12 deletions

View file

@ -12,6 +12,7 @@ abstract class EuiccChannel(
val cardId = port.card.cardId
val name = "SLOT $logicalSlotId"
val removable = port.card.isRemovable
val isMEP = port.card.isMultipleEnabledProfilesSupported
abstract val lpa: LocalProfileAssistant
val valid: Boolean

View file

@ -8,6 +8,7 @@ import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.ImageButton
import android.widget.PopupMenu
import android.widget.TextView
@ -26,7 +27,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.lang.Exception
class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesChangedListener {
open class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesChangedListener {
companion object {
const val TAG = "EuiccManagementFragment"
@ -38,7 +39,7 @@ class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesCh
private lateinit var fab: FloatingActionButton
private lateinit var profileList: RecyclerView
private val adapter = EuiccProfileAdapter(listOf())
private val adapter = EuiccProfileAdapter()
override fun onCreateView(
inflater: LayoutInflater,
@ -76,6 +77,8 @@ class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesCh
refresh()
}
protected open suspend fun onCreateFooterViews(parent: ViewGroup): List<View> = listOf()
@SuppressLint("NotifyDataSetChanged")
private fun refresh() {
swipeRefresh.isRefreshing = true
@ -88,6 +91,7 @@ class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesCh
withContext(Dispatchers.Main) {
adapter.profiles = profiles.operational
adapter.footerViews = onCreateFooterViews(profileList)
adapter.notifyDataSetChanged()
swipeRefresh.isRefreshing = false
}
@ -130,7 +134,30 @@ class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesCh
channel.lpa.disableProfile(iccid)
}
inner class ViewHolder(private val root: View) : RecyclerView.ViewHolder(root) {
sealed class ViewHolder(root: View) : RecyclerView.ViewHolder(root) {
enum class Type(val value: Int) {
PROFILE(0),
FOOTER(1);
companion object {
fun fromInt(value: Int) =
Type.values().first { it.value == value }
}
}
}
inner class FooterViewHolder: ViewHolder(FrameLayout(requireContext())) {
fun attach(view: View) {
view.parent?.let { (it as ViewGroup).removeView(view) }
(itemView as FrameLayout).addView(view)
}
fun detach() {
(itemView as FrameLayout).removeAllViews()
}
}
inner class ProfileViewHolder(private val root: View) : ViewHolder(root) {
private val iccid: TextView = root.findViewById(R.id.iccid)
private val name: TextView = root.findViewById(R.id.name)
private val state: TextView = root.findViewById(R.id.state)
@ -208,16 +235,49 @@ class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesCh
}
}
inner class EuiccProfileAdapter(var profiles: List<LocalProfileInfo>) : RecyclerView.Adapter<ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.euicc_profile, parent, false)
return ViewHolder(view)
}
inner class EuiccProfileAdapter : RecyclerView.Adapter<ViewHolder>() {
var profiles: List<LocalProfileInfo> = listOf()
var footerViews: List<View> = listOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
when (ViewHolder.Type.fromInt(viewType)) {
ViewHolder.Type.PROFILE -> {
val view = LayoutInflater.from(parent.context).inflate(R.layout.euicc_profile, parent, false)
ProfileViewHolder(view)
}
ViewHolder.Type.FOOTER -> {
FooterViewHolder()
}
}
override fun getItemViewType(position: Int): Int =
when {
position < profiles.size -> {
ViewHolder.Type.PROFILE.value
}
position >= profiles.size && position < profiles.size + footerViews.size -> {
ViewHolder.Type.FOOTER.value
}
else -> -1
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.setProfile(profiles[position])
when (holder) {
is ProfileViewHolder -> {
holder.setProfile(profiles[position])
}
is FooterViewHolder -> {
holder.attach(footerViews[position - profiles.size])
}
}
}
override fun getItemCount(): Int = profiles.size
override fun onViewRecycled(holder: ViewHolder) {
if (holder is FooterViewHolder) {
holder.detach()
}
}
override fun getItemCount(): Int = profiles.size + footerViews.size
}
}

View file

@ -11,6 +11,7 @@ import android.widget.Spinner
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import im.angry.openeuicc.common.R
import im.angry.openeuicc.core.EuiccChannel
import im.angry.openeuicc.core.EuiccChannelManager
import im.angry.openeuicc.util.*
import kotlinx.coroutines.Dispatchers
@ -72,6 +73,9 @@ open class MainActivity : AppCompatActivity() {
return true
}
protected open fun createEuiccManagementFragment(channel: EuiccChannel): EuiccManagementFragment =
EuiccManagementFragment.newInstance(channel.slotId, channel.portId)
private suspend fun init() {
withContext(Dispatchers.IO) {
manager.enumerateEuiccChannels()
@ -88,7 +92,7 @@ open class MainActivity : AppCompatActivity() {
withContext(Dispatchers.Main) {
manager.knownChannels.forEach { channel ->
spinnerAdapter.add(channel.name)
fragments.add(EuiccManagementFragment.newInstance(channel.slotId, channel.portId))
fragments.add(createEuiccManagementFragment(channel))
}
if (fragments.isNotEmpty()) {

View file

@ -0,0 +1,24 @@
package im.angry.openeuicc.ui
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import im.angry.openeuicc.R
class PrivilegedEuiccManagementFragment: EuiccManagementFragment() {
companion object {
fun newInstance(slotId: Int, portId: Int): EuiccManagementFragment =
newInstanceEuicc(PrivilegedEuiccManagementFragment::class.java, slotId, portId)
}
override suspend fun onCreateFooterViews(parent: ViewGroup): List<View> =
if (channel.isMEP) {
val view = layoutInflater.inflate(R.layout.footer_mep, parent, false)
view.findViewById<Button>(R.id.footer_mep_slot_mapping).setOnClickListener {
(requireActivity() as PrivilegedMainActivity).showSlotMappingFragment()
}
listOf(view)
} else {
listOf()
}
}

View file

@ -4,6 +4,7 @@ import android.view.Menu
import android.view.MenuItem
import android.widget.Toast
import im.angry.openeuicc.R
import im.angry.openeuicc.core.EuiccChannel
import im.angry.openeuicc.util.*
class PrivilegedMainActivity : MainActivity() {
@ -20,6 +21,9 @@ class PrivilegedMainActivity : MainActivity() {
return true
}
internal fun showSlotMappingFragment() =
SlotMappingFragment().show(supportFragmentManager, SlotMappingFragment.TAG)
override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
R.id.dsds -> {
tm.dsdsEnabled = !item.isChecked
@ -28,9 +32,12 @@ class PrivilegedMainActivity : MainActivity() {
true
}
R.id.slot_mapping -> {
SlotMappingFragment().show(supportFragmentManager, SlotMappingFragment.TAG)
showSlotMappingFragment()
true
}
else -> super.onOptionsItemSelected(item)
}
override fun createEuiccManagementFragment(channel: EuiccChannel): EuiccManagementFragment =
PrivilegedEuiccManagementFragment.newInstance(channel.slotId, channel.portId)
}

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/footer_mep_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="40dp"
android:layout_marginEnd="40dp"
android:gravity="center"
android:text="@string/footer_mep"
android:textStyle="italic"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/footer_mep_slot_mapping"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<Button
android:id="@+id/footer_mep_slot_mapping"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/slot_mapping"
android:textColor="?attr/colorAccent"
style="?android:attr/borderlessButtonStyle"
app:layout_constraintTop_toBottomOf="@id/footer_mep_text"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -6,6 +6,8 @@
<string name="toast_dsds_switched">DSDS state switched. Please wait until the modem restarts.</string>
<string name="footer_mep">Multiple Enabled Profiles (MEP) is supported by this slot. To enable or disable this feature, use the \"Slot Mapping\" tool.</string>
<string name="slot_mapping">Slot Mapping</string>
<string name="slot_mapping_logical_slot">Logical slot %d:</string>
<string name="slot_mapping_port">Slot %d Port %d</string>