web-dev-qa-db-ja.com

AndroidデュアルSIMカードAPI

Android SDKを介してデュアルSIM機能にアクセスすることについていくつかの質問があります。それらすべては、そのような機能がAndroidではサポートされていないという簡単な説明で答えられています。

これにもかかわらず、デュアルSIM電話が存在し、 MultiSim のようなアプリケーションは、これをある種の製造元に依存しない方法で検出できるようです。

それでは、その謝辞から始めて、いくつか指摘された質問をしてみましょう。

  • 「Android SDKは複数のSIM機能をサポートしていない」ということは、これらの機能が存在しないことを意味しますか、それともそれらを使用しようとするのは単に悪い考えですか?
  • SIMカード情報を提供するAndroidコンテンツプロバイダーまたは内部パッケージ(com.Android ...)はありますか?(TelephonyManagerは、ドキュメントとコードで確認できる限り、複数のSIMカードについての言及なし)
  • 複数のSIM機能を開発者に公開しているメーカーのレポートはありますか?
  • 製造元から文書化されていない機能を探す場合、どうすればよいですか?

(ちなみに、このすべてのポイントは、単にこのアルゴリズムを実装することです。SMSをSIMカード1で送信します。配信が失敗した場合は、SIMカード2に切り替えて、その方法でメッセージを再送信します。)

29
adam.baker

AndroidはAPI 22より前の複数のSIM機能をサポートしていません。ただしAndroid 5.1(APIレベル22)以降、Androidは複数のSIMのサポートを開始しました。詳細は- Androidドキュメント

これからの参照 元の回答

5
mgcaguioa

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();
    }
}
9
Tapa Save

3つの異なるカテゴリがあります...

  1. サポートおよび文書化された機能
  2. 利用可能で文書化されていない機能
  3. 利用できない機能

したがって、デュアルsim機能は使用できますが、ドキュメント化されていないため、正式にはサポートされていません。

それはそれが使用できないという意味ではありませんが、それは単にAndroid(またはそのことについてはgoogleまたは製造元)がアプリの機能をサポートする義務を負わないことを意味します。

しかし、それはうまくいくかもしれません。たとえば、連絡先は同じようなものです。

次に、ドキュメント化されていない場合、誰がどのように機能について知っているかを尋ねるかもしれません。ちょっとAndroidはオープンソースです..コードを調べて、自分で見つけてください。それは私が推測していることですマルチシム開発者がやった。

8
Gaurav Shah

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" +
            ")"
    }
}

また、私はネイティブlib依存関係を削除します

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"
        )
    }
}

それは誰かのために役立つかもしれません

0
Vlad