有几个关于通过Android SDK访问双SIM卡功能的问题,所有这些都可以通过简短的声明来回答,这些声明在Android中不支持这些功能。
尽管如此,双SIM卡手机确实存在,像MultiSim这样的应用似乎能够以某种独立于制造商的方式检测到这一点。
所以,从这个确认开始,让我试着提出一些更尖锐的问题:
(顺便说一下,所有这一切只是为了实现这个算法:用SIM卡1发送短信;如果发送失败,切换到SIM卡2并重新发送消息。)
答案 0 :(得分:9)
您可以使用MultiSim
库从多SIM卡设备获取详细信息。
每张SIM卡的可用信息:IMEI,IMSI,SIM序列号,SIM状态,SIM运营商代码,SIM运营商名称,SIM国家iso,网络运营商代码,网络运营商名称,网络运营商iso,网络类型,漫游状态
只需在应用级Gradle脚本中添加以下行:
dependencies {
compile 'com.kirianov.multisim:multisim:2.0@aar'
}
不要忘记在AndroidManifest.xml中添加所需的权限:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
在代码中使用类似的代码:
MultiSimTelephonyManager multiSimTelephonyManager = new MultiSimTelephonyManager(this);
// or
MultiSimTelephonyManager multiSimTelephonyManager = new MultiSimTelephonyManager(this, new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
updateInfo();
}
});
public void updateInfo() {
// for update UI
runOnUiThread(new Runnable() {
@Override
public void run() {
multiSimTelephonyManager.update();
useInfo();
}
}
// for update background information
multiSimTelephonyManager.update();
useInfo();
}
public void useInfo() {
// get number of slots:
if (multiSimTelephonyManager != null) {
multiSimTelephonyManager.sizeSlots();
}
// get info from each slot:
if (multiSimTelephonyManager != null) {
for(int i = 0; i < multiSimTelephonyManager.sizeSlots(); i++) {
multiSimTelephonyManager.getSlot(i).getImei();
multiSimTelephonyManager.getSlot(i).getImsi();
multiSimTelephonyManager.getSlot(i).getSimSerialNumber();
multiSimTelephonyManager.getSlot(i).getSimState();
multiSimTelephonyManager.getSlot(i).getSimOperator();
multiSimTelephonyManager.getSlot(i).getSimOperatorName();
multiSimTelephonyManager.getSlot(i).getSimCountryIso();
multiSimTelephonyManager.getSlot(i).getNetworkOperator();
multiSimTelephonyManager.getSlot(i).getNetworkOperatorName();
multiSimTelephonyManager.getSlot(i).getNetworkCountryIso();
multiSimTelephonyManager.getSlot(i).getNetworkType();
multiSimTelephonyManager.getSlot(i).isNetworkRoaming();
}
}
}
// or for devices above android 6.0
MultiSimTelephonyManager multiSimTelephonyManager = new MultiSimTelephonyManager(MyActivity.this, broadcastReceiverChange);
Usage:
// get info about slot 'i' by methods:
multiSimTelephonyManager.getSlot(i).
Force update info
// force update phone info (needed on devices above android 6.0 after confirm permissions request)
multiSimTelephonyManager.update();
Handle of permissions request (6.0+)
// in YourActivity for update info after confirm permissions request on devices above android 6.0
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (multiSimTelephonyManager != null) {
multiSimTelephonyManager.update();
}
}
答案 1 :(得分:8)
有3个不同的类别......
因此双SIM卡功能可用,但没有记录,因此没有得到官方支持。
虽然说这并不意味着它不可用,但这只意味着android(或谷歌甚至manufaturer)不负责支持你的应用程序功能。
但它可能会起作用,因为例如联系人是类似的事情。
然后你可能会问每个人如何知道这些功能,如果万一没有记录..嘿android是开源的......去看看代码,找到它自己。多数民众赞成开发人员的确如此。
答案 2 :(得分:5)
Android在API 22之前不支持多种SIM功能。但是从Android 5.1(API级别22)开始,Android开始支持多个SIM卡。有关Android Documentation
的更多详情 的参考资料答案 3 :(得分:0)
MultiSim库源现在在VCS上不可用
它的来源仍然可以在这里https://mvnrepository.com/artifact/com.kirianov.multisim/multisim
我已将其重写在Kotlin上供个人使用
class Slot {
var imei: String? = null
var imsi: String? = null
var simState = -1
val simStates = hashSetOf<Int>()
var simSerialNumber: String? = null
var simOperator: String? = null
var simCountryIso: String? = null
fun setSimState(state: Int?) {
if (state == null) {
simState = -1
return
}
simState = state
}
private fun compare(slot: Slot?): Boolean {
return if (slot != null) {
imei == slot.imei && imsi == slot.imsi && simSerialNumber == slot.simSerialNumber
} else false
}
fun indexIn(slots: List<Slot>?): Int {
if (slots == null) {
return -1
}
for (i in slots.indices) {
if (compare(slots[i])) {
return i
}
}
return -1
}
fun containsIn(slots: List<Slot>?): Boolean {
if (slots == null) {
return false
}
for (slot in slots) {
if (compare(slot)) {
return true
}
}
return false
}
override fun toString(): String {
return "Slot(" +
"imei=$imei, " +
"imsi=$imsi, " +
"simState=$simState, " +
"simStates=$simStates, " +
"simSerialNumber=$simSerialNumber, " +
"simOperator=$simOperator, " +
"simCountryIso=$simCountryIso" +
")"
}
}
我还删除了本地库依赖项
import android.Manifest
import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.content.Context
import android.os.Build
import android.telephony.SubscriptionManager
import android.text.TextUtils
import domain.shadowss.extension.areGranted
import domain.shadowss.extension.isLollipopMR1Plus
import domain.shadowss.extension.isMarshmallowPlus
import domain.shadowss.extension.isOreoPlus
import domain.shadowss.model.Slot
import org.jetbrains.anko.telephonyManager
import timber.log.Timber
import java.lang.ref.WeakReference
import java.lang.reflect.Modifier
import java.util.*
/**
* https://mvnrepository.com/artifact/com.kirianov.multisim/multisim
*/
@Suppress("MemberVisibilityCanBePrivate")
class MultiSimManager(context: Context) {
private val reference = WeakReference(context)
val slots = arrayListOf<Slot>()
@Synchronized get
@Suppress("unused")
val dualMcc: Pair<String?, String?>
@SuppressLint("MissingPermission")
@TargetApi(Build.VERSION_CODES.LOLLIPOP_MR1)
get() = reference.get()?.run {
if (isLollipopMR1Plus()) {
if (areGranted(Manifest.permission.READ_PHONE_STATE)) {
val subscriptionManager =
getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) as SubscriptionManager
val list = subscriptionManager.activeSubscriptionInfoList
return list.getOrNull(0)?.mcc?.toString()
?.padStart(3, '0') to list.getOrNull(1)?.mcc?.toString()
?.padStart(3, '0')
}
}
null to null
} ?: null to null
@Synchronized
@SuppressLint("MissingPermission")
fun updateData(): String? = reference.get()?.run {
if (areGranted(Manifest.permission.READ_PHONE_STATE)) {
val error = try {
var slotNumber = 0
while (true) {
val slot = touchSlot(slotNumber)
if (slot == null) {
for (i in slotNumber until slots.size) {
slots.removeAt(i)
}
break
}
if (slot.containsIn(slots) && slot.indexIn(slots) < slotNumber) {
// protect from Alcatel infinity bug
break
}
slots.apply {
when {
size > slotNumber -> {
removeAt(slotNumber)
add(slotNumber, slot)
}
size == slotNumber -> add(slot)
}
}
slotNumber++
}
null
} catch (e: Throwable) {
Timber.e(e)
e.toString()
}
// below is my custom logic only which was found on practice
slots.removeAll { it.imsi == null || it.simOperator?.trim()?.isEmpty() != false }
val imsi = arrayListOf<String?>()
slots.forEachReversedWithIndex { i, slot ->
if (imsi.contains(slot.imsi)) {
slots.removeAt(i)
} else {
imsi.add(slot.imsi)
slot.simStates.apply {
clear()
addAll(slots.filter { it.imsi == slot.imsi }.map { it.simState })
}
}
}
error
} else {
slots.clear()
null
}
}
@Suppress("SpellCheckingInspection", "DEPRECATION")
@SuppressLint("MissingPermission", "HardwareIds")
private fun touchSlot(slotNumber: Int): Slot? = reference.get()?.run {
val slot = Slot()
val telephonyManager = telephonyManager
Timber.v("telephonyManager [$telephonyManager] ${telephonyManager.deviceId}")
val subscriberIdIntValue = ArrayList<String>()
val subscriberIdIntIndex = ArrayList<Int>()
for (i in 0..99) {
val subscriber = runMethodReflect(
telephonyManager,
"android.telephony.TelephonyManager",
"getSubscriberId",
arrayOf(i),
null
) as? String
if (subscriber != null && !subscriberIdIntValue.contains(subscriber)) {
subscriberIdIntValue.add(subscriber)
subscriberIdIntIndex.add(i)
}
}
var subIdInt =
if (subscriberIdIntIndex.size > slotNumber) subscriberIdIntIndex[slotNumber] else null
if (subIdInt == null) {
try {
subIdInt = runMethodReflect(
telephonyManager,
"android.telephony.TelephonyManager",
"getSubId",
arrayOf(slotNumber),
null
).toString().toInt()
} catch (ignored: Throwable) {
}
}
Timber.v("subIdInt $subIdInt")
val subscriberIdLongValue = ArrayList<String>()
val subscriberIdLongIndex = ArrayList<Long>()
for (i in 0L until 100L) {
val subscriber = runMethodReflect(
telephonyManager,
"android.telephony.TelephonyManager",
"getSubscriberId",
arrayOf(i),
null
) as? String
runMethodReflect(
telephonyManager,
"android.telephony.TelephonyManagerSprd",
"getSubInfoForSubscriber",
arrayOf(i),
null
) ?: continue
if (subscriber != null && !subscriberIdLongValue.contains(subscriber)) {
subscriberIdLongValue.add(subscriber)
subscriberIdLongIndex.add(i)
}
}
if (subscriberIdLongIndex.size <= 0) {
for (i in 0L until 100L) {
val subscriber = runMethodReflect(
telephonyManager,
"android.telephony.TelephonyManager",
"getSubscriberId",
arrayOf(i),
null
) as? String
if (subscriber != null && !subscriberIdLongValue.contains(subscriber)) {
subscriberIdLongValue.add(subscriber)
subscriberIdLongIndex.add(i)
}
}
}
var subIdLong =
if (subscriberIdLongIndex.size > slotNumber) subscriberIdLongIndex[slotNumber] else null
if (subIdLong == null) {
subIdLong = runMethodReflect(
telephonyManager,
"android.telephony.TelephonyManager",
"getSubId",
arrayOf(slotNumber),
null
) as? Long
}
Timber.v("subIdLong $subIdLong")
val listParamsSubs = ArrayList<Any?>()
if (subIdInt != null && !listParamsSubs.contains(subIdInt)) {
listParamsSubs.add(subIdInt)
}
if (subIdLong != null && !listParamsSubs.contains(subIdLong)) {
listParamsSubs.add(subIdLong)
}
if (!listParamsSubs.contains(slotNumber)) {
listParamsSubs.add(slotNumber)
}
val objectParamsSubs = listParamsSubs.toTypedArray()
for (i in objectParamsSubs.indices) {
Timber.v("SPAM PARAMS_SUBS [$i]=[${objectParamsSubs[i]}]")
}
val listParamsSlot = ArrayList<Any?>()
if (!listParamsSlot.contains(slotNumber)) {
listParamsSlot.add(slotNumber)
}
if (subIdInt != null && !listParamsSlot.contains(subIdInt)) {
listParamsSlot.add(subIdInt)
}
if (subIdLong != null && !listParamsSlot.contains(subIdLong)) {
listParamsSlot.add(subIdLong)
}
val objectParamsSlot = listParamsSlot.toTypedArray()
for (i in objectParamsSlot.indices) {
Timber.v("SPAM PARAMS_SLOT [$i]=[${objectParamsSlot[i]}]")
}
// firstly all Int params, then all Long params
Timber.v("------------------------------------------")
Timber.v("SLOT [$slotNumber]")
if (isMarshmallowPlus()) {
slot.imei = telephonyManager.getDeviceId(slotNumber)
}
if (slot.imei == null) {
slot.imei = iterateMethods("getDeviceId", objectParamsSlot) as? String
}
if (slot.imei == null) {
slot.imei = runMethodReflect(
null,
"com.android.internal.telephony.Phone",
null,
null,
"GEMINI_SIM_" + (slotNumber + 1)
) as? String
}
if (slot.imei == null) {
slot.imei = runMethodReflect(
getSystemService("phone" + (slotNumber + 1)),
null,
"getDeviceId",
null,
null
) as? String
}
Timber.v("IMEI [${slot.imei}]")
if (slot.imei == null) {
when (slotNumber) {
0 -> {
slot.imei = if (isOreoPlus()) {
telephonyManager.imei
} else {
telephonyManager.deviceId
}
slot.imsi = telephonyManager.subscriberId
slot.simState = telephonyManager.simState
slot.simOperator = telephonyManager.simOperator
slot.simSerialNumber = telephonyManager.simSerialNumber
slot.simCountryIso = telephonyManager.simCountryIso
return slot
}
}
}
if (slot.imei == null) {
return null
}
slot.setSimState(iterateMethods("getSimState", objectParamsSlot) as? Int)
Timber.v("SIMSTATE [${slot.simState}]")
slot.imsi = iterateMethods("getSubscriberId", objectParamsSubs) as? String
Timber.v("IMSI [${slot.imsi}]")
slot.simSerialNumber = iterateMethods("getSimSerialNumber", objectParamsSubs) as? String
Timber.v("SIMSERIALNUMBER [${slot.simSerialNumber}]")
slot.simOperator = iterateMethods("getSimOperator", objectParamsSubs) as? String
Timber.v("SIMOPERATOR [${slot.simOperator}]")
slot.simCountryIso = iterateMethods("getSimCountryIso", objectParamsSubs) as? String
Timber.v("SIMCOUNTRYISO [${slot.simCountryIso}]")
Timber.v("------------------------------------------")
return slot
}
@SuppressLint("WrongConstant")
private fun iterateMethods(methodName: String?, methodParams: Array<Any?>): Any? =
reference.get()?.run {
if (methodName == null || methodName.isEmpty()) {
return null
}
val telephonyManager = telephonyManager
val instanceMethods = ArrayList<Any?>()
val multiSimTelephonyManagerExists = telephonyManager.toString()
.startsWith("android.telephony.MultiSimTelephonyManager")
for (methodParam in methodParams) {
if (methodParam == null) {
continue
}
val objectMulti = if (multiSimTelephonyManagerExists) {
runMethodReflect(
null,
"android.telephony.MultiSimTelephonyManager",
"getDefault",
arrayOf(methodParam),
null
)
} else {
telephonyManager
}
if (!instanceMethods.contains(objectMulti)) {
instanceMethods.add(objectMulti)
}
}
if (!instanceMethods.contains(telephonyManager)) {
instanceMethods.add(telephonyManager)
}
val telephonyManagerEx = runMethodReflect(
null,
"com.mediatek.telephony.TelephonyManagerEx",
"getDefault",
null,
null
)
if (!instanceMethods.contains(telephonyManagerEx)) {
instanceMethods.add(telephonyManagerEx)
}
val phoneMsim = getSystemService("phone_msim")
if (!instanceMethods.contains(phoneMsim)) {
instanceMethods.add(phoneMsim)
}
if (!instanceMethods.contains(null)) {
instanceMethods.add(null)
}
var result: Any?
for (methodSuffix in suffixes) {
for (className in classNames) {
for (instanceMethod in instanceMethods) {
for (methodParam in methodParams) {
if (methodParam == null) {
continue
}
result = runMethodReflect(
instanceMethod,
className,
methodName + methodSuffix,
if (multiSimTelephonyManagerExists) null else arrayOf(methodParam),
null
)
if (result != null) {
return result
}
}
}
}
}
return null
}
private fun runMethodReflect(
instanceInvoke: Any?,
classInvokeName: String?,
methodName: String?,
methodParams: Array<Any>?,
field: String?
): Any? {
var result: Any? = null
try {
val classInvoke = when {
classInvokeName != null -> Class.forName(classInvokeName)
instanceInvoke != null -> instanceInvoke.javaClass
else -> return null
}
if (field != null) {
val fieldReflect = classInvoke.getField(field)
val accessible = fieldReflect.isAccessible
fieldReflect.isAccessible = true
result = fieldReflect.get(null).toString()
fieldReflect.isAccessible = accessible
} else {
var classesParams: Array<Class<*>?>? = null
if (methodParams != null) {
classesParams = arrayOfNulls(methodParams.size)
for (i in methodParams.indices) {
classesParams[i] = when {
methodParams[i] is Int -> Int::class.javaPrimitiveType
methodParams[i] is Long -> Long::class.javaPrimitiveType
methodParams[i] is Boolean -> Boolean::class.javaPrimitiveType
else -> methodParams[i].javaClass
}
}
}
val method = if (classesParams != null) {
classInvoke.getDeclaredMethod(methodName.toString(), *classesParams)
} else {
classInvoke.getDeclaredMethod(methodName.toString())
}
val accessible = method.isAccessible
method.isAccessible = true
result = if (methodParams != null) {
method.invoke(instanceInvoke ?: classInvoke, *methodParams)
} else {
method.invoke(instanceInvoke ?: classInvoke)
}
method.isAccessible = accessible
}
} catch (ignored: Throwable) {
}
return result
}
@Suppress("unused")
val allMethodsAndFields: String
get() = """
Default: ${reference.get()?.telephonyManager}${'\n'}
${printAllMethodsAndFields("android.telephony.TelephonyManager")}
${printAllMethodsAndFields("android.telephony.MultiSimTelephonyManager")}
${printAllMethodsAndFields("android.telephony.MSimTelephonyManager")}
${printAllMethodsAndFields("com.mediatek.telephony.TelephonyManager")}
${printAllMethodsAndFields("com.mediatek.telephony.TelephonyManagerEx")}
${printAllMethodsAndFields("com.android.internal.telephony.ITelephony")}
""".trimIndent()
private fun printAllMethodsAndFields(className: String): String {
val builder = StringBuilder()
builder.append("========== $className\n")
try {
val cls = Class.forName(className)
for (method in cls.methods) {
val params = method.parameterTypes.map { it.name }
builder.append(
"M: ${method.name} [${params.size}](${TextUtils.join(
",",
params
)}) -> ${method.returnType} ${if (Modifier.isStatic(method.modifiers)) "(static)" else ""}\n"
)
}
for (field in cls.fields) {
builder.append("F: ${field.name} ${field.type}\n")
}
} catch (e: Throwable) {
builder.append("E: $e\n")
}
return builder.toString()
}
companion object {
private val classNames = arrayOf(
null,
"android.telephony.TelephonyManager",
"android.telephony.MSimTelephonyManager",
"android.telephony.MultiSimTelephonyManager",
"com.mediatek.telephony.TelephonyManagerEx",
"com.android.internal.telephony.Phone",
"com.android.internal.telephony.PhoneFactory"
)
private val suffixes = arrayOf(
"",
"Gemini",
"Ext",
"Ds",
"ForSubscription",
"ForPhone"
)
}
}
也许对某人有帮助
答案 4 :(得分:0)
您可以使用SubscriptionInfo
类来实现它,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
val mSubscriptionManager = getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) as SubscriptionManager
// val mSubscriptionManager = SubscriptionManager.from(baseContext)
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) {
val subscriptions = mSubscriptionManager.activeSubscriptionInfoList
//loop through number of SIMS inserted
for (subscriptionInfo in subscriptions) {
//the number of this subscription if the calling app has been granted the READ_PHONE_NUMBERS permission, or an empty string otherwise
Log.v("SIM", subscriptionInfo.number)
//the ISO country code
Log.v("SIM", subscriptionInfo.countryIso)
//the name displayed to the user that identifies Subscription provider name
Log.v("SIM", subscriptionInfo.carrierName.toString())
//the name displayed to the user that identifies this subscription
Log.v("SIM", subscriptionInfo.displayName.toString())
//The MCC, as a string. This value may be null.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
Log.v("SIM", subscriptionInfo.mccString.toString())
} else {
Log.v("SIM", subscriptionInfo.mcc.toString())
}
//The MNC, as a string. This value may be null.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
Log.v("SIM", subscriptionInfo.mncString.toString())
} else {
Log.v("SIM", subscriptionInfo.mnc.toString())
}
}
}
}
答案 5 :(得分:-1)
<receiver
android:name=".SimChangedReceiver"
android:enabled="true"
android:process=":remote" >
<intent-filter>
<action android:name="android.intent.action.SIM_STATE_CHANGED" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
SimChangedReceiver class
public class SimChangedReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equalsIgnoreCase("android.intent.action.SIM_STATE_CHANGED")) {
Log.d("SimChangedReceiver", "--> SIM state changed <--");
// do code whatever u want to apply action //
}
}
}
这也适用于双卡而且你不需要拨打这个接收器,因为它会远程运行