web-dev-qa-db-fra.com

Android API double carte SIM

Il y a plusieurs questions sur l'accès aux fonctionnalités de la double SIM via le SDK Android, toutes répondues par de brèves déclarations indiquant que ces fonctionnalités ne sont pas prises en charge dans Android.

Malgré cela, les téléphones à double SIM existent, et des applications comme MultiSim semblent être capables de détecter cela d'une manière indépendante du fabricant.

Donc, en commençant par cette reconnaissance, permettez-moi d'essayer de poser quelques questions plus pointues:

  • Est-ce que "Android SDK ne prend pas en charge plusieurs fonctionnalités SIM" signifie que ces fonctionnalités n'existent pas ou que c'est simplement une mauvaise idée d'essayer de les utiliser?
  • Existe-t-il un Android ou un package interne (com.Android ...) qui fournit des informations sur la carte SIM? (TelephonyManager, pour autant que je puisse voir dans les documents et le code, a aucune mention de plusieurs cartes SIM)
  • Existe-t-il un rapport d'un fabricant exposant plusieurs fonctionnalités SIM aux développeurs?
  • Si je cherchais une fonctionnalité non documentée auprès d'un fabricant, comment pourrais-je m'y prendre?

(Soit dit en passant, le but de tout cela est simplement de mettre en œuvre cet algorithme: envoyer un SMS avec la carte SIM 1; si la livraison échoue, basculez vers la carte SIM 2 et renvoyez le message de cette façon .)

29
adam.baker

Android ne prend pas en charge plusieurs fonctionnalités SIM avant l'API 22. Mais à partir de Android 5.1 (API niveau 22)), Android a commencé à prendre en charge plusieurs cartes SIM. Plus de détails sur - Documentation Android

Référence de cette réponse originale

5
mgcaguioa

Vous pouvez utiliser la bibliothèque MultiSim pour obtenir des détails sur les appareils multi-sim.

Informations disponibles sur chaque carte SIM: IMEI, IMSI, numéro de série SIM, état SIM, code opérateur SIM, nom opérateur SIM, iso pays SIM, code opérateur réseau, nom opérateur réseau, iso opérateur réseau, type de réseau, statut d'itinérance.

Ajoutez simplement les lignes ci-dessous dans votre script Gradle au niveau de l'application:

dependencies {
    compile 'com.kirianov.multisim:multisim:2.0@aar'
}

N'oubliez pas d'ajouter l'autorisation requise dans AndroidManifest.xml:

<uses-permission Android:name="Android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission Android:name="Android.permission.READ_PHONE_STATE"/>

Utilisez un code similaire dans votre code:

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

il y a 3 catégories différentes ...

  1. fonctionnalités prises en charge et documentées
  2. Fonctionnalités disponibles et non documentées
  3. fonctionnalités non disponibles

Les fonctionnalités dual sim sont donc disponibles mais non documentées et donc pas officiellement supportées.

Cela dit, cela ne signifie pas qu'il ne sera pas utilisable, cela signifie simplement qu'Android (ou d'ailleurs Google ou même le fabricant) n'est pas tenu de prendre en charge la fonctionnalité de vos applications.

Mais cela pourrait bien fonctionner, par exemple, les contacts sont une chose similaire.

Vous pourriez alors demander comment tout le monde serait au courant des fonctionnalités si au cas où cela ne serait pas documenté. Hé Android est open source. C'est ce que je suppose que les développeurs multi-sim ont fait.

8
Gaurav Shah

Les sources de la bibliothèque MultiSim ne sont plus disponibles sur VCS
Ses sources sont toujours disponibles ici https://mvnrepository.com/artifact/com.kirianov.multisim/multisim
Je l'ai réécrit sur Kotlin pour un usage personnel

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

Aussi, je supprime les dépendances de la bibliothèque native

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

Peut-être que ce sera utile pour quelqu'un

0
Vlad