Как получить номер телефона и слот для SIM-карты для текущего устройства при обратном вызове onCallAdded?

#android #phone-call #sim-card #incallservice

Вопрос:

Предыстория

Я работаю над приложением, которое реализует InCallService, чтобы оно могло обрабатывать телефонные звонки

Проблема

На устройствах с несколькими SIM-картами мне нужно показать информацию о том, какая SIM-карта и связанный с ней номер телефона используются (текущего устройства).

Дело в том, что я не могу найти, где получить эту информацию.

Что я нашел

  1. Учитывая, что я достигаю функции, подобной onCallAdded, я получаю экземпляр класса Call, поэтому мне нужно связать то, что я получаю оттуда, со слотом sim и номером телефона.
  2. Используя call.getDetails().getHandle() , я могу получить Uri, состоящий только из номера телефона другого человека, который звонил (который звонил текущему пользователю или кому звонил текущий пользователь).
  3. Я могу перебрать все SIM-карты, но не могу найти то, что я могу использовать для сопоставления между ними и текущим вызовом:
 final TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
final SubscriptionManager subscriptionManager = SubscriptionManager.from(context);
for (final SubscriptionInfo subscriptionInfo : subscriptionManager.getActiveSubscriptionInfoList()) {
    final TelephonyManager subscriptionId = telephonyManager.createForSubscriptionId(subscriptionInfo.getSubscriptionId());
}
 
  1. Был старый код, который больше не работает, который использует call.getDetails().getAccountHandle().getId() и SubscriptionManager.from(context)getActiveSubscriptionInfoList().getActiveSubscriptionInfoList() .
  2. Я думаю, что могу использовать telephonyManager.getSubscriptionId(callDetails.getAccountHandle()) , но для этого требуется API 30, что довольно ново…

Вопросы

Учитывая телефонный звонок, который я получаю от этого обратного вызова (и, возможно, других), как я могу получить соответствующий слот для SIM-карты и номер его телефона?

Я предпочитаю знать, как это сделать для максимально широкого диапазона версий Android, потому что InCallService из API 23… Это должно быть возможно до API 30, верно?

Комментарии:

1. TelecomManager.getPhoneAccount() Работает ли?

2. @vlp Кажется, что это действительно работает хорошо! К сожалению, я не могу протестировать старые устройства (у них их нет), а эмулятор не может иметь multi-SIM, но это выглядит многообещающе. Я использовал это, чтобы получить номер телефона: telecomManager.getPhoneAccount(call.details.accountHandle)? .адрес?.schemeSpecificPart а затем я нашел его в списке SubscriptionManager.activeSubscriptionInfoList в числовом поле. Почему вы не написали это в ответах? И как вы об этом узнали? Я пытался искать об этом часами…

3. Честно говоря, я читал об этом в официальной документации и никогда не пробовал. Я рад, что это работает для вас. Удачи в вашем проекте!

4. @vlp Пожалуйста, напишите в своем ответе, откуда вы его получили.

5. У меня есть эта информация исключительно из официальной документации Android.

Ответ №1:

Используйте call.getDetails().getAccountHandle() для получения PhoneAccountHandle.

Затем используйте TelecomManager.getPhoneAccount(), чтобы получить экземпляр PhoneAccount.


Разрешение Manifest.permission.READ_PHONE_NUMBERS требуется для приложений, ориентированных на уровень API 31 .


Отказ от ответственности: я не эксперт по Android, поэтому, пожалуйста, подтвердите мои мысли.


РЕДАКТИРОВАТЬ: таким образом, решение как для API 30, так и для API 30 может быть таким:

 @RequiresApi(Build.VERSION_CODES.M)
fun handleCall(context: Context, call: Call) {
    var foundAndSetSimDetails = false
    val callDetailsAccountHandle = callDetails.accountHandle
    val subscriptionManager = context
        .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) as SubscriptionManager
    val telephonyManager =
        context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
    val telecomManager =
        context.getSystemService(Context.TELECOM_SERVICE) as TelecomManager
    val hasReadPhoneStatePermission =
        ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) == android.content.pm.PackageManager.PERMISSION_GRANTED
    val phoneAccount = telecomManager.getPhoneAccount(callDetailsAccountHandle)
    //TODO when targeting API 31, we might need to check for a new permission here, of READ_PHONE_NUMBERS
    //find SIM by phone account
    if (!foundAndSetSimDetails amp;amp; phoneAccount != null amp;amp; hasReadPhoneStatePermission) {
        val callCapablePhoneAccounts = telecomManager.callCapablePhoneAccounts
        run {
            callCapablePhoneAccounts?.forEachIndexed { index, phoneAccountHandle ->
                if (phoneAccountHandle != callDetailsAccountHandle)
                    return@forEachIndexed
                if (!phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION))
                    return@run
                //found the sim card index
                simName = phoneAccount.label?.toString().orEmpty()
                simIndex = index   1
                foundAndSetSimDetails = true
                return@run
            }
        }
    }
    //find SIM by subscription ID
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R amp;amp; hasReadPhoneStatePermission) {
        try {
            val callSubscriptionId: Int =
                telephonyManager.getSubscriptionId(callDetailsAccountHandle!!)
            for (subscriptionInfo: SubscriptionInfo in subscriptionManager.activeSubscriptionInfoList) {
                val activeSubscriptionId: Int = subscriptionInfo.subscriptionId
                if (activeSubscriptionId == callSubscriptionId) {
                    setSimDetails(telephonyManager, subscriptionInfo)
                    foundAndSetSimDetails = true
                    break
                }
            }
        } catch (e: Throwable) {
            e.printStackTrace()
        }
    }
    //find SIM by phone number
    if (!foundAndSetSimDetails amp;amp; hasReadPhoneStatePermission) {
        try {
            val simPhoneNumber: String? = phoneAccount?.address?.schemeSpecificPart
            if (!simPhoneNumber.isNullOrBlank()) {
                for (subscriptionInfo in subscriptionManager.activeSubscriptionInfoList) {
                    if (simPhoneNumber == subscriptionInfo.number) {
                        setSimDetails(telephonyManager, subscriptionInfo)
                        foundAndSetSimDetails = true
                        break
                    }
                }
                if (!foundAndSetSimDetails)
            }
        } catch (e: Throwable) {
            e.printStackTrace()
        }
    }

    private fun setSimDetails(telephonyManager: TelephonyManager, subscriptionInfo: SubscriptionInfo) {
        var foundSimName: String? = null
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            val telephonyManagerForSubscriptionId =
                telephonyManager.createForSubscriptionId(subscriptionInfo.subscriptionId)
            foundSimName = telephonyManagerForSubscriptionId.simOperatorName
        }
        if (foundSimName.isNullOrBlank())
            foundSimName = subscriptionInfo.carrierName?.toString()
        simName = if (foundSimName.isNullOrBlank())
            ""
        else foundSimName
        simIndex = subscriptionInfo.simSlotIndex   1
    }
 

Комментарии:

1. Для какой функции, как вы видите, нам нужно READ_PHONE_NUMBERS разрешение? Кроме того, потребуется ли это, даже если я использую относительно новый API, of telephonyManager.getSubscriptionId , который я нахожу в списке subscriptionManager.activeSubscriptionInfoList элементов, проверяя их subscriptionId ?

2. В документации указано, что TelecomManager.getPhoneAccount() требуется READ_PHONE_NUMBERS на уровне API 31 , TelephonyManager.getSubscriptionId потребности READ_PHONE_STATE и SubscriptionManager.getActiveSubscriptionInfoList() потребности READ_PHONE_STATE или привилегии оператора. Также обратите внимание, что SubscriptionInfo.getNumber() для планируемого использования требуются некоторые разрешения ( READ_PHONE_STATE ниже уровня API 30 или одно из READ_PRIVILEGED_PHONE_STATE / READ_PHONE_NUMBERS / READ_SMS на уровне API 30 ) или привилегии оператора.

3. Итак, для telephonyManager.getSubscriptionId(callDetails.getAccountHandle()) последующего поиска в SubscriptionManager.getActiveSubscriptionInfoList() подходе вам теоретически потребуется READ_PHONE_STATE ( TelephonyManager.getSubscriptionId SubscriptionManager.getActiveSubscriptionInfoList() ), а затем соответствующие привилегии для чтения информации из SubsciptionInfo ( READ_PRIVILEGED_PHONE_STATE / READ_PHONE_NUMBERS / READ_SMS , чтобы получить номер телефона). Предполагается, что уровень API 30 . Пожалуйста, перекрестно подтвердите мои мысли.

4. Я отредактировал ваш ответ, чтобы включить полный код для обоих случаев. Что касается разрешений, спасибо. В обоих случаях требуется READ_PHONE_STATE при настройке API 30. Я думаю, что при таргетинге на API 31 только getPhoneAccount это будет проблематично, но тогда оно используется только для случая до API 30, так что все должно быть в порядке. Я попробовал сейчас, и он предупредил меня только о READ_PHONE_STATE разрешении, поэтому не знаю, есть ли в этом коде больше возможных проблем. Сообщается здесь: issuetracker.google.com/issues/205726880 . Если у вас есть какие-либо другие вещи, которые вы считаете важными, пожалуйста, дайте мне знать.

5. Google ответил мне о том, как это сделать, но не уверен в том, что они написали: issuetracker.google.com/issues/204791554#comment5