forked from PeterCxy/OpenEUICC
[3/n] Initial implementation of MEP-based slot mapping
This commit is contained in:
parent
4090418146
commit
53fa754197
|
@ -57,6 +57,8 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.2'
|
||||
compileOnly project(':libs:hidden-apis-stub')
|
||||
implementation project(':libs:hidden-apis-shim')
|
||||
implementation project(':libs:lpac-jni')
|
||||
|
|
|
@ -27,6 +27,10 @@ class PrivilegedMainActivity : MainActivity() {
|
|||
finish()
|
||||
true
|
||||
}
|
||||
R.id.slot_mapping -> {
|
||||
SlotMappingFragment().show(supportFragmentManager, SlotMappingFragment.TAG)
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
159
app/src/main/java/im/angry/openeuicc/ui/SlotMappingFragment.kt
Normal file
159
app/src/main/java/im/angry/openeuicc/ui/SlotMappingFragment.kt
Normal file
|
@ -0,0 +1,159 @@
|
|||
package im.angry.openeuicc.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
import android.telephony.TelephonyManager
|
||||
import android.telephony.UiccSlotMapping
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.AdapterView
|
||||
import android.widget.AdapterView.OnItemSelectedListener
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.Spinner
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.appcompat.widget.Toolbar.OnMenuItemClickListener
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import im.angry.openeuicc.OpenEuiccApplication
|
||||
import im.angry.openeuicc.R
|
||||
import im.angry.openeuicc.util.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class SlotMappingFragment: DialogFragment(), OnMenuItemClickListener {
|
||||
companion object {
|
||||
const val TAG = "SlotMappingFragment"
|
||||
}
|
||||
|
||||
private val tm: TelephonyManager by lazy {
|
||||
(requireContext().applicationContext as OpenEuiccApplication).telephonyManager
|
||||
}
|
||||
|
||||
private val ports: List<UiccPortInfoCompat> by lazy {
|
||||
tm.uiccCardsInfoCompat.flatMap { it.ports }
|
||||
}
|
||||
|
||||
private val portsDesc: List<String> by lazy {
|
||||
ports.map { getString(R.string.slot_mapping_port, it.card.physicalSlotIndex, it.portIndex) }
|
||||
}
|
||||
|
||||
private lateinit var toolbar: Toolbar
|
||||
private lateinit var recyclerView: RecyclerView
|
||||
private lateinit var adapter: SlotMappingAdapter
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val view = inflater.inflate(R.layout.fragment_slot_mapping, container, false)
|
||||
toolbar = view.findViewById(R.id.toolbar)
|
||||
toolbar.inflateMenu(R.menu.fragment_slot_mapping)
|
||||
recyclerView = view.findViewById(R.id.mapping_list)
|
||||
recyclerView.layoutManager =
|
||||
LinearLayoutManager(view.context, LinearLayoutManager.VERTICAL, false)
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
toolbar.title = getString(R.string.slot_mapping)
|
||||
toolbar.setNavigationOnClickListener { dismiss() }
|
||||
toolbar.setOnMenuItemClickListener(this)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
setWidthPercent(85)
|
||||
init()
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private fun init() {
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
val mapping = withContext(Dispatchers.IO) {
|
||||
tm.simSlotMapping
|
||||
}
|
||||
|
||||
adapter = SlotMappingAdapter(mapping.toMutableList().apply {
|
||||
sortBy { it.logicalSlotIndex }
|
||||
})
|
||||
recyclerView.adapter = adapter
|
||||
adapter.notifyDataSetChanged()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private fun commit() {
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
withContext(Dispatchers.IO) {
|
||||
tm.simSlotMapping = adapter.mappings
|
||||
}
|
||||
openEuiccApplication.euiccChannelManager.invalidate()
|
||||
requireActivity().finish()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMenuItemClick(item: MenuItem?): Boolean =
|
||||
when (item!!.itemId) {
|
||||
R.id.ok -> {
|
||||
commit()
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
|
||||
inner class ViewHolder(root: View): RecyclerView.ViewHolder(root), OnItemSelectedListener {
|
||||
private val textViewLogicalSlot: TextView = root.findViewById(R.id.slot_mapping_logical_slot)
|
||||
private val spinnerPorts: Spinner = root.findViewById(R.id.slot_mapping_ports)
|
||||
|
||||
init {
|
||||
spinnerPorts.adapter = ArrayAdapter(requireContext(), im.angry.openeuicc.common.R.layout.spinner_item, portsDesc)
|
||||
spinnerPorts.onItemSelectedListener = this
|
||||
}
|
||||
|
||||
private lateinit var mappings: MutableList<UiccSlotMapping>
|
||||
private var mappingId: Int = -1
|
||||
|
||||
fun attachView(mappings: MutableList<UiccSlotMapping>, mappingId: Int) {
|
||||
this.mappings = mappings
|
||||
this.mappingId = mappingId
|
||||
|
||||
textViewLogicalSlot.text = getString(R.string.slot_mapping_logical_slot, mappings[mappingId].logicalSlotIndex)
|
||||
spinnerPorts.setSelection(ports.indexOfFirst {
|
||||
it.card.physicalSlotIndex == mappings[mappingId].physicalSlotIndex
|
||||
&& it.portIndex == mappings[mappingId].portIndex
|
||||
})
|
||||
}
|
||||
|
||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||
check(this::mappings.isInitialized) { "mapping not assigned" }
|
||||
mappings[mappingId] =
|
||||
UiccSlotMapping(
|
||||
ports[position].portIndex, ports[position].card.physicalSlotIndex, mappings[mappingId].logicalSlotIndex)
|
||||
}
|
||||
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
inner class SlotMappingAdapter(val mappings: MutableList<UiccSlotMapping>): RecyclerView.Adapter<ViewHolder>() {
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val view = LayoutInflater.from(parent.context).inflate(R.layout.fragment_slot_mapping_item, parent, false)
|
||||
return ViewHolder(view)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = mappings.size
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
holder.attachView(mappings, position)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ import android.os.Build
|
|||
import android.telephony.IccOpenLogicalChannelResponse
|
||||
import android.telephony.TelephonyManager
|
||||
|
||||
// TODO: Usage of *byPort APIs will still break build in-tree on lower AOSP versions
|
||||
// TODO: Usage of new APIs from T or later will still break build in-tree on lower AOSP versions
|
||||
// Maybe older versions should simply include hidden-apis-shim when building?
|
||||
fun TelephonyManager.iccOpenLogicalChannelByPortCompat(
|
||||
slotIndex: Int, portIndex: Int, aid: String?, p2: Int
|
||||
|
|
29
app/src/main/res/layout/fragment_slot_mapping.xml
Normal file
29
app/src/main/res/layout/fragment_slot_mapping.xml
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?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="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/Theme.OpenEUICC"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:elevation="4dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintWidth_percent="1"
|
||||
app:navigationIcon="?homeAsUpIndicator" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/mapping_list"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="6dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/toolbar"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
25
app/src/main/res/layout/fragment_slot_mapping_item.xml
Normal file
25
app/src/main/res/layout/fragment_slot_mapping_item.xml
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?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="48sp"
|
||||
android:gravity="center">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:orientation="horizontal">
|
||||
<TextView
|
||||
android:id="@+id/slot_mapping_logical_slot"
|
||||
android:textSize="14sp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="32sp"
|
||||
android:layout_marginEnd="10sp" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/slot_mapping_ports"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="32sp" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
|
@ -7,4 +7,8 @@
|
|||
android:checkable="true"
|
||||
android:visible="false"
|
||||
app:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/slot_mapping"
|
||||
android:title="@string/slot_mapping"
|
||||
app:showAsAction="never" />
|
||||
</menu>
|
9
app/src/main/res/menu/fragment_slot_mapping.xml
Normal file
9
app/src/main/res/menu/fragment_slot_mapping.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/ok"
|
||||
android:icon="@drawable/ic_check_black"
|
||||
android:title="@string/slot_mapping"
|
||||
app:showAsAction="ifRoom"/>
|
||||
</menu>
|
|
@ -5,4 +5,8 @@
|
|||
<string name="dsds">Dual SIM</string>
|
||||
|
||||
<string name="toast_dsds_switched">DSDS state switched. Please wait until the modem restarts.</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>
|
||||
</resources>
|
|
@ -31,7 +31,7 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
compileOnly project(':libs:hidden-apis-stub')
|
||||
implementation 'androidx.core:core-ktx:1.7.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.4.2'
|
||||
implementation 'com.google.android.material:material:1.6.1'
|
||||
|
|
|
@ -3,6 +3,7 @@ package im.angry.openeuicc.util
|
|||
import android.telephony.IccOpenLogicalChannelResponse
|
||||
import android.telephony.SubscriptionManager
|
||||
import android.telephony.TelephonyManager
|
||||
import android.telephony.UiccSlotMapping
|
||||
import java.lang.reflect.Method
|
||||
|
||||
// Hidden APIs via reflection to enable building without AOSP source tree
|
||||
|
@ -46,6 +47,17 @@ private val iccTransmitApduLogicalChannelByPort: Method by lazy {
|
|||
Int::class.java, Int::class.java, Int::class.java, String::class.java
|
||||
)
|
||||
}
|
||||
private val getSimSlotMapping: Method by lazy {
|
||||
TelephonyManager::class.java.getMethod(
|
||||
"getSimSlotMapping"
|
||||
)
|
||||
}
|
||||
private val setSimSlotMapping: Method by lazy {
|
||||
TelephonyManager::class.java.getMethod(
|
||||
"setSimSlotMapping",
|
||||
Collection::class.java
|
||||
)
|
||||
}
|
||||
|
||||
fun TelephonyManager.iccOpenLogicalChannelBySlot(
|
||||
slotId: Int, appletId: String?, p2: Int
|
||||
|
@ -79,6 +91,10 @@ fun TelephonyManager.iccTransmitApduLogicalChannelByPort(
|
|||
this, slotId, portId, channel, cla, instruction, p1, p2, p3, data
|
||||
) as String?
|
||||
|
||||
var TelephonyManager.simSlotMapping: Collection<UiccSlotMapping>
|
||||
get() = getSimSlotMapping.invoke(this) as Collection<UiccSlotMapping>
|
||||
set(new) { setSimSlotMapping.invoke(this, new) }
|
||||
|
||||
private val requestEmbeddedSubscriptionInfoListRefresh: Method by lazy {
|
||||
SubscriptionManager::class.java.getMethod("requestEmbeddedSubscriptionInfoListRefresh", Int::class.java)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
package android.telephony;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
public final class UiccSlotMapping implements Parcelable {
|
||||
public static final Creator<UiccSlotMapping> CREATOR = null;
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
throw new RuntimeException("stub");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param portIndex The port index is an enumeration of the ports available on the UICC.
|
||||
* @param physicalSlotIndex is unique index referring to a physical SIM slot.
|
||||
* @param logicalSlotIndex is unique index referring to a logical SIM slot.
|
||||
*
|
||||
*/
|
||||
public UiccSlotMapping(int portIndex, int physicalSlotIndex, int logicalSlotIndex) {
|
||||
throw new RuntimeException("stub");
|
||||
}
|
||||
|
||||
/**
|
||||
* Port index is the unique index referring to a port belonging to the physical SIM slot.
|
||||
* If the SIM does not support multiple enabled profiles, the port index is default index 0.
|
||||
*
|
||||
* @return port index.
|
||||
*/
|
||||
public int getPortIndex() {
|
||||
throw new RuntimeException("stub");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the physical slot index for the slot that the UICC is currently inserted in.
|
||||
*
|
||||
* @return physical slot index which is the index of actual physical UICC slot.
|
||||
*/
|
||||
public int getPhysicalSlotIndex() {
|
||||
throw new RuntimeException("stub");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets logical slot index for the slot that the UICC is currently attached.
|
||||
* Logical slot index is the unique index referring to a logical slot(logical modem stack).
|
||||
*
|
||||
* @return logical slot index;
|
||||
*/
|
||||
public int getLogicalSlotIndex() {
|
||||
throw new RuntimeException("stub");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
throw new RuntimeException("stub");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
throw new RuntimeException("stub");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
throw new RuntimeException("stub");
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue